使用SQLite锁定行(读取锁?)
我用Python开发了一个简单的代理测试工具。代理的IP和端口,还有它们的date_of_last_test
(比如说31/12/2011 10:10:10)和result_of_last_test
(结果是OK或者KO)都存储在一个SQLite表里。(我知道我可以存储更多的测试结果细节,保持历史记录和统计数据,但这个简单的模型已经满足我的需求了。)
下面是测试工具主循环的简化代码,我在这里循环遍历代理并更新它们的状态:
while True:
# STEP 1: select
myCursor.execute("SELECT * from proxy ORDER BY date_of_last_test ASC;")
row = myCursor.fetchone()
# STEP 2: update
if isProxyWorking(row['ip'], row['port']): # this test can last a few seconds
updateRow(row['ip'], row['port'], 'OK')
else:
updateRow(row['ip'], row['port'], 'KO')
我的代码在作为单个进程运行时效果很好。现在,我想能够同时运行多个程序进程,使用同一个SQLite数据库文件。目前代码的问题是缺少一个锁机制,这样就会导致多个进程同时测试同一个代理。
在步骤1 / SELECT时,最干净的加锁方式是什么,这样下一个进程在进行SELECT时能获取到下一个代理?
换句话说,我想避免以下情况:
假设现在是晚上10点,数据库里有两个代理:
代理A
最后测试是在晚上8点,代理B
最后测试是在晚上9点。
我启动两个测试进程来更新它们的状态:
- 10:00 - 进程1获取到“最旧”的代理进行测试:
A
- 10:01 -
进程2获取到“最旧”的代理进行测试:!!!
A
!!!(在这里我希望进程2能获取到代理B
,因为A
已经在被测试了 - 虽然在数据库中还没有更新) - 10:10 - 进程1对
A
的测试结束,状态在数据库中更新 - 10:11 - 进程2对
A
的测试结束,状态在数据库中又一次更新(!!! 再次 !!!)
在这种情况下并没有实际的错误或异常,但我想避免浪费时间。
1 个回答
SQLite数据库一次只能让一个进程进行更新。根据常见问题解答,
多个进程可以同时打开同一个数据库,也可以同时进行查询(SELECT)。但是在任何时刻,只有一个进程可以对数据库进行修改。
而且,
当SQLite尝试访问一个被其他进程锁定的文件时,默认的行为是返回 SQLITE_BUSY(忙碌)。你可以通过C代码中的sqlite3_busy_handler()或sqlite3_busy_timeout()这两个函数来调整这个行为。
所以如果只是少量的更新,这样的方式是可以的;但如果更新比较频繁,你就需要考虑换一个更强大的数据库了。
总之,数据库只有一个锁,这个锁是针对整个数据库的。