【发布时间】:2014-01-04 09:19:33
【问题描述】:
所以我使用的是 psycopg2,我有一个简单的表:
CREATE TABLE IF NOT EXISTS feed_cache (
feed_id int REFERENCES feeds(id) UNIQUE,
feed_cache text NOT NULL,
expire_date timestamp --without time zone
);
我正在调用以下方法和查询:
@staticmethod
def get_feed_cache(conn, feed_id):
c = conn.cursor()
try:
sql = 'SELECT feed_cache FROM feed_cache WHERE feed_id=%s AND localtimestamp <= expire_date;'
c.execute(sql, (feed_id,))
result = c.fetchone()
if result:
conn.commit()
return result[0]
else:
print 'DBSELECT.get_feed_cache: %s' % result
print 'sql: %s' % (c.mogrify(sql, (feed_id,)))
except:
conn.rollback()
raise
finally:
c.close()
return None
我添加了 else 语句来输出正在执行和返回的确切 sql 和结果。
从数据库连接线程池调用 get_feed_cache() 方法。当 get_feed_cache() 方法被“缓慢”调用(~1/sec 或更少)时,结果会按预期返回,但是当同时调用时,它偶尔会返回 None。我尝试了多种编写此查询和方法的方法。
一些观察:
- 如果我从查询中删除 'AND localtimestamp
- 在 psql 中以串行方式快速执行查询总是返回结果。
- 在阅读了 psycopg 游标类的 fetch*() 方法后,他们注意到结果是为游标缓存的,我假设缓存不在不同游标之间共享。 http://initd.org/psycopg/docs/faq.html#best-practices
- 我尝试过使用 postgresql 的 now() 和 current_timestamp 函数,结果相同。 (我知道 now() 和 current_timestamp 的时区方面)
需要注意的条件:
- 绝不会出现所提供的 feed_id 没有 feed_cache 值的情况。
- 永远不会出现 feed_cache 表中的任何值为 NULL 的情况
- 在测试时,我完全禁用了对该表的任何和所有写入
- 对于所有值,我已将 expire_date 设置为足够远的未来,这样表达式“AND localtimestamp
这是它的复制和粘贴输出,返回无:
DBSELECT.get_feed_cache: None
sql: SELECT feed_cache FROM feed_cache WHERE feed_id=5 AND localtimestamp < expire_date;
差不多就是这样,我不确定发生了什么。也许我犯了一些非常愚蠢的错误,而我只是没有注意到! 我目前的猜测是它与 psycopg2 以及它在游标之间缓存结果的方式有关。如果游标确实共享缓存并且查询几乎同时发生,那么第一个游标可能会获取结果,第二个游标看到有相同查询的缓存,所以它不执行,然后第一个游标关闭并删除缓存,第二个游标尝试获取现在为 null/None 的缓存。*
也就是说,psycopg2 声明它对于只读查询是线程安全的,所以除非我错误地解释了他们的线程安全实现,否则情况不应该如此。
感谢您的宝贵时间!
*给get_feed_cache加线程锁后,先获取再创建游标,再释放再返回,偶尔还是会得到None结果
【问题讨论】:
-
您确定表中没有 NULL 值吗?
SELECT * FROM feed_cache WHERE feed_cache IS NULL说什么?我问这个是因为你的代码看起来不错,我觉得你的数据有问题。 -
SELECT * FROM feed_cache WHERE feed_cache 为空; feed_id | feed_cache | expire_date ---------+------------+-------------(0行)
-
抱歉格式化,返回0行。
-
这里是数据库中所有内容的链接。 pastebin.com/Dwt7B84g
-
好吧,看了之后,我想我会尝试将 expire_date 放在更远的将来(一个月),一切都很完美。所以我猜这与时区有关。通常将 expire_date 设置为未来 30 分钟。
标签: python sql multithreading postgresql psycopg2