MySQL實現(xiàn)分布式鎖的三種主流方案
前言:
MySQL 實現(xiàn)分布式鎖的核心是利用數(shù)據(jù)庫的原子性、唯一性約束或行級鎖機制,保證分布式系統(tǒng)中多個節(jié)點對共享資源的互斥訪問,解決跨進程、跨服務(wù)器的并發(fā)競爭問題。以下是三種主流、可靠且覆蓋不同業(yè)務(wù)場景的實現(xiàn)方案,包含原理、完整實現(xiàn)、注意事項及適用場景對比,兼顧實用性和嚴(yán)謹(jǐn)性。
方案一:基于唯一索引的 INSERT 實現(xiàn)(最常用 / 推薦)
核心原理
利用 MySQL 唯一索引(UNIQUE INDEX)的唯一性約束實現(xiàn)鎖的互斥:為分布式鎖創(chuàng)建專用表,將鎖標(biāo)識(如資源名)設(shè)為唯一索引,多個節(jié)點同時嘗試插入同一鎖標(biāo)識的記錄時,只有一個節(jié)點能插入成功(獲鎖),其余節(jié)點因唯一索引沖突插入失?。〒屾i失?。会尫沛i時刪除該記錄,若服務(wù)宕機未主動釋放,可通過過期時間實現(xiàn)鎖的自動釋放,避免死鎖。
1. 鎖表結(jié)構(gòu)設(shè)計(必建)
需包含鎖標(biāo)識(唯一)、過期時間、業(yè)務(wù)附加信息,同時為鎖標(biāo)識創(chuàng)建唯一索引,保證插入的原子性:
CREATE TABLE `distributed_lock` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主鍵', `lock_key` VARCHAR(64) NOT NULL COMMENT '分布式鎖標(biāo)識(如:order:1001、stock:20)', `expire_time` DATETIME NOT NULL COMMENT '鎖過期時間(避免服務(wù)宕機死鎖)', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間', PRIMARY KEY (`id`), UNIQUE KEY `uk_lock_key` (`lock_key`) -- 核心:唯一索引,保證同一lock_key只能插入一條 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT 'MySQL分布式鎖表';
2. 獲取鎖(INSERT 原子操作)
通過INSERT語句嘗試插入鎖記錄,插入成功即獲取鎖,插入失?。ㄎㄒ凰饕龥_突)則表示鎖已被其他節(jié)點占用。需指定合理的過期時間(大于業(yè)務(wù)執(zhí)行的最大耗時,如 5 秒、10 秒):
-- 嘗試獲取鎖:lock_key為具體資源標(biāo)識,expire_time為當(dāng)前時間+過期時長(示例:5秒)
INSERT INTO distributed_lock (lock_key, expire_time)
VALUES ('order:1001', DATE_ADD(NOW(), INTERVAL 5 SECOND));
程序處理邏輯:
執(zhí)行 INSERT 后,若受影響行數(shù) = 1 → 獲鎖成功,執(zhí)行業(yè)務(wù)邏輯;
若拋出唯一索引沖突異常(或受影響行數(shù) = 0)→ 獲鎖失敗,可重試 / 放棄。
3. 釋放鎖(DELETE 主動釋放)
業(yè)務(wù)執(zhí)行完成后,主動刪除對應(yīng) lock_key 的記錄,釋放鎖供其他節(jié)點使用:
-- 釋放鎖:根據(jù)lock_key精準(zhǔn)刪除,避免誤刪其他鎖 DELETE FROM distributed_lock WHERE lock_key = 'order:1001';
4. 鎖超時自動釋放(解決死鎖)
若服務(wù)在執(zhí)行業(yè)務(wù)時宕機 / 網(wǎng)絡(luò)中斷,無法主動執(zhí)行 DELETE 釋放鎖,此時當(dāng)expire_time小于當(dāng)前時間,鎖即失效。其他節(jié)點可通過先清理過期鎖,再嘗試獲鎖的邏輯優(yōu)化搶鎖流程:
-- 優(yōu)化版獲鎖:先刪除指定lock_key的過期鎖,再插入(保證鎖的可用性)
-- 步驟1:清理該lock_key的過期鎖(通過定時腳本等)
DELETE FROM distributed_lock WHERE lock_key = 'order:1001' AND expire_time < NOW();
-- 步驟2:嘗試插入新鎖
INSERT INTO distributed_lock (lock_key, expire_time)
VALUES ('order:1001', DATE_ADD(NOW(), INTERVAL 5 SECOND));
關(guān)鍵注意事項
過期時間必須大于業(yè)務(wù)實際最大執(zhí)行耗時,否則會出現(xiàn) “業(yè)務(wù)未執(zhí)行完,鎖已過期被其他節(jié)點獲取” 的并發(fā)問題;
釋放鎖時必須根據(jù) lock_key 精準(zhǔn)刪除,禁止無條件 DELETE 或按 id 刪除,避免誤刪其他節(jié)點的鎖;
可通過 ** 樂觀鎖(加版本號)** 進一步優(yōu)化,防止多個節(jié)點同時清理過期鎖后重復(fù)插入(極端場景);
優(yōu)點:實現(xiàn)簡單、無侵入、支持集群(主從同步即可)、天然防死鎖;缺點:高并發(fā)下可能存在輕微的鎖競爭(可通過重試機制緩解)。
方案二:基于 MySQL 自帶函數(shù) GET_LOCK/RELEASE_LOCK
核心原理
MySQL 提供了專用的鎖函數(shù)GET_LOCK(key, timeout)和RELEASE_LOCK(key),基于 ** 數(shù)據(jù)庫連接(Session)** 實現(xiàn)分布式鎖:
GET_LOCK(key, timeout):嘗試獲取名為key的鎖,超時時間為timeout秒(0 表示立即返回,-1 表示永久阻塞);同一key只能被一個連接持有,其他連接嘗試獲取會阻塞 / 失??;
RELEASE_LOCK(key):主動釋放名為key的鎖,釋放成功返回 1,鎖不存在返回 0,不是當(dāng)前連接持有返回 NULL;
若持有鎖的連接斷開(正常 / 異常),MySQL 會自動釋放該連接持有的所有鎖,天然防死鎖。
1. 獲取鎖(GET_LOCK)
-- 嘗試獲取鎖:key為鎖標(biāo)識,timeout=5秒(5秒內(nèi)獲取不到則返回0)
SELECT GET_LOCK('order:1001', 5);
返回值說明:
1 → 獲鎖成功;
0 → 超時未獲取到鎖;
NULL → 執(zhí)行出錯(如數(shù)據(jù)庫連接異常)。
2. 釋放鎖(RELEASE_LOCK)
-- 主動釋放鎖:key與獲取鎖時一致
SELECT RELEASE_LOCK('order:1001');
3. 強制釋放鎖(針對異常場景)
若需手動釋放其他連接持有的鎖,可通過KILL連接實現(xiàn)(需數(shù)據(jù)庫管理員權(quán)限):
-- 步驟1:查詢持有指定鎖的連接ID
SELECT PROCESSLIST_ID FROM INFORMATION_SCHEMA.PROCESSLIST
WHERE STATE = CONCAT('Waiting for release of advisory lock for key ', QUOTE('order:1001'));
-- 步驟2:KILL該連接(自動釋放鎖)
KILL 1234; -- 1234為查詢到的連接ID
關(guān)鍵注意事項
鎖與數(shù)據(jù)庫連接強綁定:若使用連接池,需保證獲鎖、執(zhí)行業(yè)務(wù)、釋放鎖使用同一個連接(連接池不能中途回收連接);
不支持分布式集群(主從 / 多實例):GET_LOCK 的鎖信息僅存儲在當(dāng)前 MySQL 實例的內(nèi)存中,主從復(fù)制不會同步該鎖信息,多實例場景下會出現(xiàn)鎖失效;
鎖粒度為字符串 key,支持任意自定義標(biāo)識,實現(xiàn)簡單;
優(yōu)點:輕量(無需建表)、原子性強、自動釋放鎖;缺點:僅支持單機 MySQL、依賴數(shù)據(jù)庫連接、無過期時間(除連接斷開外)。
方案三:基于悲觀鎖(SELECT … FOR UPDATE)
核心原理
利用 MySQL InnoDB 引擎的行級排他鎖實現(xiàn)分布式鎖:通過SELECT … FOR UPDATE語句查詢指定資源記錄并加排他鎖,多個節(jié)點同時執(zhí)行該語句時,只有一個節(jié)點能獲取到行鎖(獲鎖成功),其余節(jié)點會被阻塞,直到鎖被釋放;鎖的釋放由事務(wù)提交 / 回滾控制,天然保證業(yè)務(wù)與鎖的一致性。
1. 前置條件
必須使用InnoDB 引擎(MyISAM 不支持事務(wù)和行級鎖);
被查詢的字段必須創(chuàng)建索引(主鍵 / 唯一索引),否則會升級為表鎖,導(dǎo)致并發(fā)性能急劇下降;
必須在顯式事務(wù)中執(zhí)行(START TRANSACTION / BEGIN),否則 SELECT 后會立即自動提交事務(wù),鎖被釋放。
2. 獲取鎖(SELECT … FOR UPDATE + 事務(wù))
先創(chuàng)建業(yè)務(wù)關(guān)聯(lián)表(或使用現(xiàn)有業(yè)務(wù)表),確保查詢字段有索引,然后在事務(wù)中加鎖:
-- 示例:基于現(xiàn)有訂單表實現(xiàn)鎖,order_id為主鍵(索引) BEGIN; -- 開啟顯式事務(wù) -- 嘗試獲取鎖:查詢指定order_id并加行級排他鎖,無記錄則返回空(可根據(jù)業(yè)務(wù)插入) SELECT * FROM `order` WHERE order_id = 1001 FOR UPDATE;
程序處理邏輯:
執(zhí)行 SELECT 后,若成功返回記錄 → 獲鎖成功,執(zhí)行業(yè)務(wù)邏輯;
若被阻塞 → 等待其他節(jié)點釋放鎖,直到超時(由 innodb_lock_wait_timeout 參數(shù)控制,默認(rèn) 50 秒);
若拋出鎖等待超時異常 → 獲鎖失敗,可重試 / 放棄。
3. 釋放鎖(事務(wù)提交 / 回滾)
業(yè)務(wù)執(zhí)行完成后,提交事務(wù)釋放行鎖;若業(yè)務(wù)執(zhí)行失敗,回滾事務(wù)也會釋放鎖,避免死鎖:
COMMIT; -- 業(yè)務(wù)成功,提交事務(wù)釋放鎖 -- ROLLBACK; -- 業(yè)務(wù)失敗,回滾事務(wù)釋放鎖
關(guān)鍵注意事項
必須精準(zhǔn)查詢索引字段:若查詢條件無索引(如 SELECT … FOR UPDATE WHERE name = ‘test’,name 無索引),會觸發(fā)全表掃描并加表鎖,導(dǎo)致所有節(jié)點阻塞,嚴(yán)重影響性能;
控制事務(wù)執(zhí)行時長:事務(wù)未提交前,鎖會一直持有,需避免在事務(wù)中執(zhí)行耗時操作(如遠程調(diào)用、文件 IO);
適用于業(yè)務(wù)與數(shù)據(jù)庫操作強綁定的場景:鎖的生命周期與事務(wù)一致,無需額外管理鎖的釋放;
優(yōu)點:與業(yè)務(wù)表融合(無需單獨建鎖表)、行級鎖并發(fā)性能高、事務(wù)一致性強;缺點:依賴事務(wù)、需關(guān)注索引設(shè)計、阻塞式等待(無非阻塞搶鎖方式)。
通用設(shè)計原則(必遵守)
原子性:獲鎖、釋放鎖操作必須是原子的(MySQL 的 INSERT、GET_LOCK、SELECT … FOR UPDATE 均天然保證原子性),避免多節(jié)點同時獲鎖;
超時釋放:必須設(shè)計鎖超時機制(唯一索引的 expire_time、GET_LOCK 的連接斷開、悲觀鎖的事務(wù)超時),杜絕死鎖;
精準(zhǔn)釋放:釋放鎖時必須根據(jù)鎖標(biāo)識(lock_key / 資源 ID)精準(zhǔn)操作,禁止批量刪除 / 釋放,避免誤刪其他節(jié)點的鎖;
細粒度鎖:鎖的標(biāo)識需盡可能細(如 order:1001 而非 order),減少鎖競爭,提高并發(fā)性能;
異常處理:業(yè)務(wù)執(zhí)行過程中若出現(xiàn)異常(如宕機、網(wǎng)絡(luò)中斷、超時),需保證鎖能被自動釋放,不影響后續(xù)節(jié)點搶鎖;
重試機制:獲鎖失敗時,可增加有限次數(shù)的重試邏輯(加隨機延遲),避免瞬時競爭導(dǎo)致的業(yè)務(wù)失敗,同時防止無限重試壓垮數(shù)據(jù)庫。
總結(jié)
首選方案:基于唯一索引的 INSERT 實現(xiàn),兼顧實現(xiàn)簡單、集群支持、防死鎖,適配 90% 以上的分布式鎖場景,是工業(yè)界主流選擇;
輕量單機場景:選擇GET_LOCK/RELEASE_LOCK,無需建表,操作便捷,但僅支持單機 MySQL;
業(yè)務(wù)與數(shù)據(jù)庫強綁定場景:選擇悲觀鎖 SELECT … FOR UPDATE,利用行級鎖保證事務(wù)與鎖的一致性,需重點關(guān)注索引和事務(wù)時長;
無論選擇哪種方案,都必須遵守原子性、超時釋放、精準(zhǔn)釋放三大核心原則,否則會出現(xiàn)鎖失效、死鎖、并發(fā)沖突等問題。
補充:MySQL 分布式鎖適用于并發(fā)量中等、對性能要求不是極致的場景;若為超高并發(fā)(如每秒數(shù)萬次搶鎖),建議選擇 Redis/ZooKeeper 分布式鎖,性能和可靠性更優(yōu)。
到此這篇關(guān)于MySQL實現(xiàn)分布式鎖的三種主流方案的文章就介紹到這了,更多相關(guān)MySQL實現(xiàn)分布式鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mysql數(shù)據(jù)庫中字符集亂碼問題原因及解決
這篇文章主要介紹了mysql數(shù)據(jù)庫中字符集亂碼問題原因及解決,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
MySQL自動填充create_time和update_time的兩種方式
當(dāng)我們創(chuàng)建業(yè)務(wù)表的時候 通常都需要設(shè)置create_time 和 update_time,下面這篇文章主要給大家介紹了關(guān)于MySQL自動填充createTime和updateTime的兩種方式,需要的朋友可以參考下2022-05-05
MySQL多表關(guān)聯(lián)查詢方式及實際應(yīng)用
MySQL語句學(xué)習(xí)的難點和重點就在于多表查詢,同時MySQL也有諸多方法供大家選擇,不論是多表聯(lián)查(聯(lián)結(jié)表、左連接、右連接……),這篇文章主要給大家介紹了關(guān)于MySQL多表關(guān)聯(lián)查詢方式及實際應(yīng)用的相關(guān)資料,需要的朋友可以參考下2024-07-07

