Mysql

生成連續票號時如何避免死鎖?

  • December 18, 2021

我已經實現了一個票務系統,當您參加活動並根據數量下訂單時,將生成*n個條目。*範例:您下單數量為 10,將生成一個 10 的票號。規則是,票號從 1 開始,每次生成時都會增加 1。每個事件的票號始終以 1 開頭,每個訂單可以有多個條目。

所以目前,為了生成票號。對於每個事件。我使用 MySQL 觸發器來增加它的值:

CREATE TRIGGER number_generator BEFORE INSERT ON entries
FOR EACH ROW BEGIN
   SET NEW.ticket_no = (SELECT COALESCE(MAX(ticket_no), 0) + 1 FROM entries WHERE event_id = NEW.event_id);
END;

這是我如何觸發票號的範例原始查詢。一代

insert into
 `entries` (`order_id`, `event_id`, `ticket_no`)
values
 (123, 1, 1),
 (123, 1, 1),
 (123, 1, 1),
 ...

問題:當多個使用者同時下單時,系統會隨機拋出死鎖異常:

序列化失敗:1213 嘗試獲取鎖時發現死鎖;嘗試重啟事務

在我的舊實現中,它使用 MyISAM 表並在第二列上自動遞增,我不會遇到這個問題,而且票號生成速度更快。如何解決死鎖並在插入時實現 MyIsam 速度,但使用 InnoDB?


訂單表定義

 CREATE TABLE `orders` (
     `id` bigint unsigned NOT NULL AUTO_INCREMENT,  
     `event_id` bigint unsigned NOT NULL, 
     `first_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
     `last_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
     `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL
     PRIMARY KEY (`id`),
     KEY `owning_event` (`event_id`)
   ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

條目表定義

CREATE TABLE `entries` (
 `event_id` bigint unsigned NOT NULL,
 `ticket_no` bigint unsigned NOT NULL,
 `order_id` bigint unsigned NOT NULL,
 PRIMARY KEY (`event_id`,`ticket_no`),
 KEY `entries_event_id_index` (`event_id`),
 KEY `entries_order_id_index` (`order_id`),
 CONSTRAINT `entries_order_id_foreign` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`),
 CONSTRAINT `entries_event_id_foreign` FOREIGN KEY (`event_id`) REFERENCES `events` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

舊條目表定義

CREATE TABLE entries_old (
   event_id BIGINT UNSIGNED NOT NULL,
   ticket_no MEDIUMINT NOT NULL AUTO_INCREMENT,
   order_id BIGINT NOT NULL,
   PRIMARY KEY (event_id, ticket_no)
) ENGINE=MyISAM;

現在您知道為什麼SELECT COALESCE(MAX(ticket_no), 0) + 1 FROM entries不是模擬序列的正確方法了;它很慢並且容易出現鎖爭用和競爭條件。你將不得不重新考慮你的方法。

首先,“票號從 1 開始,每次生成時加 1”的規則似乎是任意的。您也可以通過正常auto_increment列唯一地辨識票證。

如果出於某種原因您堅持執行此規則,您可能會選擇將“下一張票號”偽序列保留為表中的一列events,但這也不能保證無間隙序列。

使用 InnoDB;使用交易;使用FOR UPDATE.

試一試……再來一張桌子。根據你的例子,它會有

event_id  order_id  tkt_start  tkt_ct
1         1         1          2
1         2         3          1
1         3         4          1
1         6         5          1
2         4         1          1
2         5         2          1

PRIMARY KEY(event_id, tkt_start) 

使用一個事務向該表中插入一行*。它將涉及一個MAX和其他混亂的東西。但是,通過只插入一行,它將不太*可能陷入僵局。可能它可以是一個單一的聲明。

如果事務是死鎖的犧牲品,您必須編寫程式碼來檢查錯誤並重新啟動事務。

然後,在單獨的事務中建構表中的多行。這不太可能被阻止,因為這些插入不太可能與其他操作發生衝突。

您的交易不得跨越使用者與其配偶核對以查看票證是否正常的時間。如果您需要“保留”以後可能會被取消的票證,則需要一種不同於單個 InnoDB 事務的鎖定機制。

如果您需要“預訂/聊天/確認”序列,那麼您必須願意失去票號的連續性。(出售座位時需要這樣做;我不明白您是否需要票號無間隙。)

引用自:https://dba.stackexchange.com/questions/295362