这是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之后执行提交可能有效,但我不完全确定。
有更优雅的解决方案吗?
答案 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中看到完整的片段集并重新组合并处理该消息两次。
这对于单表问题来说非常复杂,但似乎有效。