SQL表锁定竞争条件 - SELECT然后INSERT

时间:2016-08-26 05:18:33

标签: mysql sql

这是MariaDB / MySQL的一个整洁的锁定问题。

服务器正在重新组合多部分SMS消息。邮件分段到达。具有相同“smsfrom”和“uniqueid”的段是同一消息的一部分。细分的段号从1开始到“细分”。当消息的所有段都到达时,消息就完成了。我们有一个等待重组的不匹配段表,如下所示:

    CREATE TABLE frags (
       smsfrom TEXT,
       uniqueid VARCHAR(32) NOT NULL,
       smsbody TEXT,
       segmentnum INTEGER NOT NULL,
       segmenttotal INTEGER NOT NULL);

当新细分进入时,我们会在交易中

   SELECT ... FROM frags WHERE smsfrom = % AND uniqueid = %;

这使我们获得了目前为止收到的所有细分。如果是新的 加上这些包含所有段号,我们有完整的信息。 我们发送消息以进行进一步处理并删除所涉及的片段。细

如果还没有到达所有片段,我们会对我们刚刚获得的片段进行INSERT。自动提交已关闭,因此两个操作都是事务的一部分。顺便提一下InnoDB引擎。

这有竞争条件。对于两段消息,两个段同时进入,并由不同的进程处理。进程A执行SELECT,什么也没找到。进程B执行SELECT,什么也没找到。进程A插入段1,没问题。进程B插入段2,没问题。现在我们陷入困境 - 所有细分都在表中,但我们没有注意到。所以消息永远停留在那里。 (在实践中,我们每隔几分钟进行一次清除,以删除旧的无与伦比的东西,但暂时忽略它。)

那有什么不对? SELECTs不会锁定任何行,因为它们什么都找不到。 我们需要一行尚不存在的行锁。将FOR UPDATE添加到SELECT没有帮助;无法锁定。 LOCK IN SHARE MODE也没有。即使是事务类型的SERIALIZABLE也无济于事,因为那只是全局锁定共享模式。

好的,假设我们首先执行INSERT,然后执行SELECT以查看是否包含所有段。进程A执行INSERT为1,没问题。进程B插入2,没问题。进程A执行SELECT,只看到1.进程B执行SELECT,只看到2.这是可重复的读语义。不好。

在执行任何此操作之前,蛮力方法是LOCK TABLE。这应该有用,虽然它很烦人,因为我在涉及其他表的事务中,而LOCK TABLE意味着提交。

在每个INSERT之后执行提交可能有效,但我不完全确定。

有更优雅的解决方案吗?

2 个答案:

答案 0 :(得分:1)

为什么不

1)过程1.插入到frag表中。没有别的

插入....    提交;

2)过程2    这可以通过

找到完整的多部分短信

通过smsfrom从frags组中选择smsfrom,unique,uniqueid,count(),唯一,唯一有计数()== segmenttotal;

将它们移动到新表

从frags中删除smsfrom =<>和unique =<&gt ;;

提交;

答案 1 :(得分:0)

正如我上面所写,我最终做到了这一点:

INSERT  ... -- Insert new fragment.
COMMIT
SELECT ... FROM frags WHERE smsfrom = % AND uniqueid = % FOR UPDATE;

检查SELECT是否返回了一组完整的片段。如果是,请重新组合并处理消息,然后

DELETE ... FROM FRAGS WHERE smsfrom = % AND uniqueid = %;

COMMIT和FOR UPDATE都是必需的。需要COMMIT,以便每个进程看到来自另一个进程的任何INSERT。 SELECT上需要FOR UPDATE来锁定所有片段,直到完成DELETE。否则,两个进程可能会在SELECT中看到完整的片段集并重新组合并处理该消息两次。

这对于单表问题来说非常复杂,但似乎有效。