2016-08-26 58 views
2

這是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後執行提交可能會有效,但我不完全確定。

有沒有更優雅的解決方案?

+0

如果您將支票的其他部分移至提交後,該怎麼辦?需要一些方法來確保進程A和B不會同時處理消息。 – Greg

回答

1

爲什麼不是

1)過程1.插入到您的碎片表。沒有別的

插入.... 承諾;

2)方法2這 找到

選擇smsfrom,獨特,UNIQUEID完整多SMS,通過從smsfrom斷枝組計數(),獨特的,具有獨特的計數()== segmenttotal;

它們移動到新的表

從斷支刪除其中smsfrom = <>和獨特= <>;

commit;

+0

我最終做了這樣的事情 - 插入,提交,然後選擇一個進程。必須做一個選擇FOR UPDATE以獲得對另一個SELECT的行鎖定,或者兩個進程都可以找到組裝的消息並且它將被處理兩次。 –

+0

非常高興聽到這有助於....我不會問你對OOB(帶外)多部分消息做了什麼,在成功重新組裝原始文件後到達:) - 您可能需要考慮它們或簡單刪除超過2天的任何事情 - 這取決於您的業務需求。 –

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。 FOR UPDATE需要在SELECT行上鎖定所有的片段,直到DELETE完成。否則,兩個進程可能會在SELECT中看到完整的片段集合,並重新組合並處理該消息兩次。

這對於單表問題來說非常複雜,但似乎工作。