使用SQLite锁定行(读取锁?)

0 投票
1 回答
3847 浏览
提问于 2025-04-17 09:19

我用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 个回答

1

SQLite数据库一次只能让一个进程进行更新。根据常见问题解答

多个进程可以同时打开同一个数据库,也可以同时进行查询(SELECT)。但是在任何时刻,只有一个进程可以对数据库进行修改。

而且,

当SQLite尝试访问一个被其他进程锁定的文件时,默认的行为是返回 SQLITE_BUSY(忙碌)。你可以通过C代码中的sqlite3_busy_handler()或sqlite3_busy_timeout()这两个函数来调整这个行为。

所以如果只是少量的更新,这样的方式是可以的;但如果更新比较频繁,你就需要考虑换一个更强大的数据库了。

总之,数据库只有一个锁,这个锁是针对整个数据库的。

撰写回答