[英]Insert with Subselect - Atomic Operation?
我知道,mysql支持自動增量值,但沒有相關的自動增量值。
即,如果您有一個這樣的表:
id | element | innerId
1 | a | 1
2 | a | 2
3 | b | 1
然后插入另一個b
-element,您需要自己計算 innerId(預期插入為“ 2”)
實現此行為的最佳方法是什么? 我不知道元素的數量,因此我無法為它們創建專用表,而我只能在其中提供ID。
(這個例子很簡單)
應該實現的目標是任何元素“類型”(數量未知, possibly infitine -1
都應具有自己的無間隙id)。
如果我會使用類似
INSERT INTO
myTable t1
(id,element, innerId)
VALUES
(null, 'b', (SELECT COUNT(*) FROM myTable t2 WHERE t2.element = "b") +1)
http://sqlfiddle.com/#!2/2f4543/1
在所有情況下,這都將返回預期的結果嗎? 我的意思是可以,但是並發呢? 具有SubSelects的插入是否仍然是原子的,或者是否存在szenario,其中兩個插入將嘗試插入相同的ID? (特別是是否有待處理的事務插入?)
嘗試使用編程語言(例如Java)實現這一目標會更好嗎? 還是更容易在盡可能接近數據庫引擎的地方實現這種邏輯?
由於我正在使用聚合來計算下一個innerId ,因此我認為使用SELECT...FOR UPDATE
不能避免在其他事務具有未決提交的情況下出現問題,對嗎?
詩:我可以。 只是強行插入-從每個元素的當前最大值開始-對(element,innerId)
具有唯一的鍵約束(element,innerId)
直到沒有foreignKey-violation-但是沒有更好的方法嗎?
根據根據另一個ID通過auto_increment設置一個ID-可能嗎? 在我的情況下,可以通過使用一個組合主鍵innerId and element
。 但是根據此設置,MySQL auto_increment依賴於僅適用於MyIsam的其他兩個主鍵 (我有InnoDB)
現在我更加困惑了。 我嘗試使用2種不同的php腳本通過上述查詢插入數據。 為了使我能夠調用腳本2(應該模擬並發修改),腳本1處於“睡眠”狀態15秒鍾- 使用一個查詢時結果正確。
(ps .: mysql(?!i)
-僅用於快速調試的功能)
基本數據:
腳本1:
mysql_query("START TRANSACTION");
mysql_query("INSERT INTO insertTest (id, element, innerId, fromPage)VALUES(null, 'a', (SELECT MAX(t2.innerID) FROM insertTest t2 WHERE element='a') +1, 'page1')");
sleep(15);
//mysql_query("ROLLBACK;");
mysql_query("COMMIT;");
腳本2:
//mysql_query("START TRANSACTION");
mysql_query("INSERT INTO insertTest (id, element, innerId, fromPage)VALUES(null, 'a', (SELECT MAX(t2.innerID) FROM insertTest t2 WHERE element='a') +1, 'page2')");
//mysql_query("COMMIT;");
我本以為page2
插入會在page1
插入之前發生,因為它運行時沒有任何事務。 但是實際上,page1插入是最先發生的,導致第二個腳本也被延遲了約15秒...
(忽略AC-Id,大約播放了一下)
在第一個腳本上使用Rollback
時,第二個腳本仍然延遲15秒, 然后選擇正確的innerId
:
因此 :
SELECT
會被阻止呢? 使用選擇並在這樣的單獨的非事務性語句中插入(在第2頁上,模擬並發修改):
$nextId = mysql_query("SELECT MAX(t2.innerID) as a FROM insertTest t2 WHERE element='a'");
$nextId = mysql_fetch_array($nextId);
$nextId = $nextId["a"] +1;
mysql_query("INSERT INTO insertTest (id, element, innerId, fromPage)VALUES(null, 'a', $nextId, 'page2')");
導致我試圖避免的錯誤:
那么,當每個修改都是一個查詢時,為什么它在並發szenario中起作用? 帶有子選擇的插入是原子的嗎?
好吧,所有(或幾乎所有)數據庫都支持根據您的規則計算innerid
的必要功能。 這稱為觸發器,特別是插入前觸發器。
您的特定版本在多用戶環境中將無法正常運行。 開始插入時,很少有數據庫會在表上生成讀取鎖。 這意味着兩個非常靠近發出的insert語句將為innerid
生成相同的值。
考慮到並發性,您應該在數據庫中使用觸發器而不是在應用程序端進行此計算。
您始終可以在需要時計算innerid
,而不是在插入值時進行計算。 這在計算上很昂貴,需要order by
(使用變量)進行order by
或使用相關子查詢。 其他數據庫支持窗口/分析功能,使這種計算更容易表達。
從我在這里看到的內容: 原子性INSERT / UPDATE查詢中的多個MySQL子查詢? 您的查詢似乎是原子的。 我已經在我的帶有InnoDB的MySQL上對它進行了測試,並使用4種不同的程序嘗試分別執行該查詢100000次。 之后,我能夠在(element,innerid)上創建一個組合的唯一鍵,並且效果很好,因此似乎沒有生成重復鍵。 但是我有:
嘗試獲取鎖時發現死鎖
因此,您可能需要考慮使用此http://dev.mysql.com/doc/refman/5.1/en/innodb-deadlocks.html
編輯:似乎我可以通過將SQL更改為來避免死鎖
INSERT INTO test(id,element,innerId)VALUES(null,“ b”,(SELECT Count(*)from test t2 WHERE element ='b'FOR UPDATE )+1);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.