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

