生成連續票號時如何避免死鎖?
我已經實現了一個票務系統,當您參加活動並根據數量下訂單時,將生成*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 事務的鎖定機制。
如果您需要“預訂/聊天/確認”序列,那麼您必須願意失去票號的連續性。(出售座位時需要這樣做;我不明白您是否需要票號無間隙。)