這是MariaDB/MySQL的一個簡潔的鎖定問題。SQL表鎖定競爭條件 - SELECT然後INSERT
服務器正在重新組裝多部分SMS消息。消息分段到達。具有相同「smsfrom」和「uniqueid」的分段是同一消息的一部分。分段的分段編號從1開始直到「segmenttotal」。當消息的所有段都到達時,消息就完成了。我們有匹配的段等待重組的表,如下所示:
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。 Autocommit已關閉,因此這兩項操作都是交易的一部分。 InnoDB引擎,順便說一下。
這有一個競爭條件。兩個段同時進入兩段消息,並由不同的進程處理。進程A執行SELECT,什麼也找不到。進程B執行SELECT,什麼也找不到。過程A插入段1,沒問題。過程B插入段2,沒問題。現在我們被卡住了 - 所有細分都在表格中,但我們沒有注意到。所以這條消息永遠停留在那裏。 (在實踐中,我們每隔幾分鐘清除一次,以刪除舊的不匹配的東西,但暫時忽略它。)
那麼,怎麼了? SELECTs不鎖定行,因爲它們什麼都沒找到。 我們需要一行尚不存在的行鎖。將FOR UPDATE添加到SELECT沒有幫助;沒什麼可鎖的。在共享模式下也不是。即使轉到SERIALIZABLE的交易類型也沒有幫助,因爲這只是全局鎖定共享模式。
好的,假設我們先執行INSERT,然後做一個SELECT來看看我們是否有所有的段。進程A執行INSERT 1,沒有問題。進程B插入2,沒問題。進程A執行一個SELECT,並只看到1.進程B執行一個SELECT,並且只看到2.這是可重複的讀取語義。不好。
在做任何這些之前,蠻力方法是一個LOCK TABLE。這應該是有效的,儘管它很煩人,因爲我在涉及其他表的事務中,而LOCK TABLE意味着提交。
在每個INSERT後執行提交可能會有效,但我不完全確定。
有沒有更優雅的解決方案?
如果您將支票的其他部分移至提交後,該怎麼辦?需要一些方法來確保進程A和B不會同時處理消息。 – Greg