Type something to search...
INSERT…ON DUPLICATE KEY UPDATE ..在 MySQL 5.7.26(Aurora MySQL 2.10.3) 後的行為差異

INSERT…ON DUPLICATE KEY UPDATE ..在 MySQL 5.7.26(Aurora MySQL 2.10.3) 後的行為差異

  • Database
  • 09 Jun, 2023

MySQL 5.7.26 修復了一個 bug #2596684release note),Aurora MySQL 在 2.10.3(release note)也修復了這個 bug。

行為差異

這個 bug fix 後,會有一些行為影響變化:

  • 在 table 有 primary key 和 unique index 的 column 時,insert on duplicated update 遇到 duplicated key record 後,會上一個 primary index 的 lock 在 supremum pseudo-record(最後一個 record 到無窮大的 gap)。因而導致其他 insert 需要等這個 lock 釋放。
  1. 在多個 insert on duplicated update 一次 insert 多筆資料時,會很容易產生出 deadlock。

對此,有人回報了另一個 bug,覺得因為 #2596684 bug 修復後,導致使用 insert on duplicate key update 會更頻繁的產生 deadlock。在這個 bug report 內,Jakub Lopuszanski 很詳細的解釋了關於 bug #2596684 修復的脈絡跟如何避免 deadlock 的建議,很推薦可以詳細看。

簡單說明

第一點 supremum pseudo-record 的 lock,借用裡面的例子來解釋。

假設有一表:

create table t1(
 f1 int auto_increment primary key,
 f2 int unique key,
 f3 int
);

有一筆資料:

(1, 10, 100)

此時如果有兩個 connection

con1:
    begin;
    insert into t1 values(2, 10, 200) on duplicate key update f3 = 120;
    commit;
    
con2:
    begin;
    insert into t1 values(2, 20, 300) on duplicate key update f3 = 500;
    commit;

在只知道 con1 con2 transaction 最後 commit 的順序,但不確定實際兩個 insert 的順序時,會因為 con2 的 insert 跟 con1 的 insert 的執行順序導致不一樣的結果。

如果 con1 的 insert 先執行,會因為 con1 insert 的 duplicate key 是 f2。所以有這樣的結果:

(1,10,120)
(2,20,300)

但如果是 con2 的 insert 執行,因為 con1 insert 的 duplicate key 變成是 f1(primary key)了,所以結果變成:

(1,10,100)
(2,20,120)

為了防止這個情況,一個做法就是要在 insert on duplicate key update 時,遇到 duplicate key 後,要 lock 住 primary index 的 supremum pseudo-record。防止被插入新的 primary key 因而導致會影響這種不確定的結果。(我講的可能不太精確,推薦大家去看原始的解說)。

至於第二點,更頻繁的 deadlock。原因就是第一點的 primary index supremum pseudo-record lock。如果有兩個 insert on duplicate key update,同時先 insert 了第一筆資料後,都拿到 primary index supremum pseudo-record lock,要繼續 insert 第二筆資料,就會因為要等對方的 primary index supremum pseudo-record lock 釋放,所以導致 deadlock。

如何解決

對於怎麼要避開 insert on duplicated update 導致的問題,Jakub Lopuszanski 也有很多建議(在 bug report 搜尋 **[4 Sep 2020 10:11] **),例如:

  • 改用 insert & update 取代 insert on duplicate key update :BEGIN; SELECT rows conflicting on secondary indexes; UPDATE found conflicting rows; INSERT new rows; COMMIT;
  • 直接把 unique column 當成 id
  • 用 READ COMMITTED isolation level

相關問題

另外,這個 bug 修復,同時也解決了以前另一個 deadlock 的問題,有一些相關文章有提到這個問題 post1 post2 post3 post4

Tags :
Share :

Related Posts

[MySQL] 使用 update_all & subquery 可能會鎖住全表
[MySQL] 使用 update_all & subquery 可能會鎖住全表

團隊最近遇到一個 subquery + update_all 會鎖整張表的問題,花了一點時間研究 注意:以下為 MySQL 5.6 的實驗探討,在 MySQL 8.0.21 的這個機制已經被優化了 問題 假設有一份 schema 長這樣,有 users 和 posts 兩張表。 create_table "posts", options: "ENGINE=

read more
[MySQL] 對不存在的 row 加鎖,會造成 Deadlock
[MySQL] 對不存在的 row 加鎖,會造成 Deadlock

[MySQL] 對不存在的 row 加鎖,會造成 Deadlock 一個範例 初始資料設定 CREATE TABLE `users` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL DEFAUL

read more
[MySQL] 跑個 migration 怎麼就倒站了
[MySQL] 跑個 migration 怎麼就倒站了

如果網站正在 migrate 時,剛好遇到一個 transaction 沒有結束,就算只是 SELECT ,也有可能讓網站倒站。 一個血淋淋的例子 某個沒有經驗的工程師 A: 對 users table 做一些簡單的查詢,但是開啟了 transaction 沒有 commit BEGIN; SELECT * FROM users; ...**正在

read more
Postgresql 常用命令
Postgresql 常用命令

$createdb [database_name]$pg_dump [database_name] -f [backup_filename]$psql -f [backup_filename] [database_name]$dropdb [database_name]

read more
在 MySQL 5.6 使用 GROUP BY 的筆記
在 MySQL 5.6 使用 GROUP BY 的筆記

在 MySQL 5.6,如果使用了 GROUP BY A 欄位,但是 SELECT 的 **B 欄位,沒有加上 aggregate function,且群組裡 row 彼此間 B 欄位 **的值並不相同,則會以 nondeterministic 的方式決定要取出 **B 欄位 **的值。 可參考文件:[https://dev.mysql.com/doc/refman/5.6/en

read more
MySQL 你到底是在鎖屁喔
MySQL 你到底是在鎖屁喔

剛剛在研究這個簡單的範例: CREATE TABLE `posts` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `content` varchar(255) DEFAULT NULL, `user_id` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `index_posts_on

read more
[Rails][MySQL] 使用 update_all + JOIN + LIMIT 需注意效能問題
[Rails][MySQL] 使用 update_all + JOIN + LIMIT 需注意效能問題

MySQL 使用 JOIN + update_all 時,若遇到 LIMIT 則會轉為 subquery,需注意效能問題。 Rails 在使用 update_all 時,如果有用到 JOIN,會改用 subquery 的形式改寫,但若 Adapter 為 MySQL 時就會維持,因為 MySQL 語法本身支援 JOIN + UPDATE。但若是包含 LIMIT 時,由於 MySQL 也不支援,所

read more