簡體   English   中英

插入子選擇-原子操作?

[英]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

在此處輸入圖片說明

因此

  • 事務處於活動狀態時,Non-Transactional-Insert被阻止。
  • 具有子選擇的插入似乎也被阻止。
  • 因此,最后似乎帶有子選擇的Insert是原子操作? 還是為什么第二頁的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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM