我这两天以来一直在努力解决这个问题。我们有一个解决方案,多个工作线程将尝试从单个数据库/表中选择作业请求,方法是在所选请求上设置一个标志,从而有效地阻止其他工作人员选择相同的请求。
我创建了一个java测试应用程序来测试我的查询,但是在正常情况下,测试执行没有问题,在高争用情况下(例如,1个包含50个线程的表条目;没有延迟或处理)我仍然有获得的线程相同的请求/输入,有趣的是它发生在测试刚刚开始时。我不明白为什么。我已经阅读了所有相关的Postgres锁定和隔离相关文档......虽然问题可能与测试应用程序本身有关,但我怀疑我在READ COMMITTED中缺少SELECT FOR UPDATE的工作方式。隔离背景。
所以问题是SELECT FOR UPDATE(使用READ COMMITED isolation)可以保证像我描述的一般并发问题可以安全地解决吗?
获取查询:
UPDATE mytable SET status = 'LOCK'
WHERE ctid IN (SELECT ctid FROM mytable
WHERE status = 'FREE'
ORDER BY id
LIMIT %d
FOR UPDATE)
RETURNING id, status;
发布查询:
UPDATE mytable SET status = 'FREE'
WHERE id = %d AND status = 'LOCK'
RETURNING id, status;
那么你会认为这两个查询应该是安全的,还是有一些奇怪的情况可以允许两个线程获取同一行?我想提一下,我也试过SERIALIZABLE隔离并没有帮助。
答案 0 :(得分:2)
事实证明(我怎么可能有所不同?)我在测试中犯了一个错误。我没有尊重资源获取/发布顺序。测试是在发布查询之后注册发布(递减计数器),这导致另一个线程获取资源并在此期间注册它。类别中的错误,您知道如何解决,但即使您多次看也看不到,因为您编写了代码...同行评审最终有所帮助。
我想在这个时候我有一个测试证明:
我不确定这对其他人是否有帮助,但我变得绝望了。所以Postgres 9.3一切都很好:)。
答案 1 :(得分:1)
我想分享的另一个方面是关于使用LIMIT 2获取查询的速度。查看测试结果:
Starting test...
DB setup done
All threads created & connections made
All threads started
Thread[36] 186/190/624=1000
Thread[19] 184/201/615=1000
Thread[12] 230/211/559=1000
Thread[46] 175/200/625=1000
Thread[ 9] 205/211/584=1000
...
Thread[ 4] 189/232/579=1000
Thread[ 3] 185/198/617=1000
Thread[49] 218/204/578=1000
Thread[ 1] 204/203/593=1000
...
Thread[37] 177/163/660=1000
Thread[31] 168/199/633=1000
Thread[18] 174/187/639=1000
Thread[42] 178/229/593=1000
Thread[29] 201/229/570=1000
...
Thread[10] 203/198/599=1000
Thread[25] 215/210/575=1000
Thread[27] 248/191/561=1000
...
Thread[17] 311/192/497=1000
Thread[ 8] 365/198/437=1000
Thread[15] 389/176/435=1000
All threads finished
Execution time: 31408
Test done; exiting
将上述内容与此查询进行比较:
UPDATE mytable SET status = 'LOCK'
WHERE id IN (SELECT t1.id FROM (SELECT id FROM mytable
WHERE status = 'FREE' ORDER BY id LIMIT 2) AS t1
FOR UPDATE)
RETURNING id, status;
结果:
Starting test...
DB setup done
All threads created & connections made
All threads started
Thread[29] 32/121/847=1000
Thread[22] 61/151/788=1000
Thread[46] 36/114/850=1000
Thread[41] 57/132/811=1000
Thread[24] 49/146/805=1000
Thread[13] 47/135/818=1000
...
Thread[20] 48/118/834=1000
Thread[47] 65/152/783=1000
Thread[18] 51/146/803=1000
Thread[ 8] 69/158/773=1000
Thread[14] 56/158/786=1000
Thread[ 0] 66/161/773=1000
Thread[38] 60/148/792=1000
Thread[27] 69/158/773=1000
...
Thread[45] 78/177/745=1000
Thread[30] 96/162/742=1000
...
Thread[32] 162/167/671=1000
Thread[17] 329/156/515=1000
Thread[33] 337/178/485=1000
Thread[37] 381/172/447=1000
All threads finished
Execution time: 15490
Test done; exiting
测试打印每个线程获取查询返回的次数为2次,1次或0次总计测试循环次数(1000)。
从上面的结果我们可以得出结论,我们可以加快查询速度(减半时间!),但代价是增加了线程争用。这意味着我们将从Acquire查询返回更多次0资源。从技术上讲,这不是问题,因为我们无论如何都需要处理这种情况。
当然,如果在没有返回资源的情况下添加等待时间(休眠),情况会发生变化,但选择正确的等待时间值取决于应用程序性能要求...