最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

MySQL實現(xiàn)可重入鎖的實踐指南

 更新時間:2026年03月17日 09:27:29   作者:java修仙傳  
本文介紹了如何在MySQL中實現(xiàn)可重入鎖,包括鎖表設(shè)計、加鎖和解鎖邏輯以及為什么必須使用事務(wù)來保證鎖的互斥性、原子性和生命周期,需要的朋友可以參考下

在分布式系統(tǒng)和并發(fā)編程中,鎖是保證數(shù)據(jù)一致性的關(guān)鍵工具。而基于 MySQL 實現(xiàn)的可重入鎖,不僅能滿足跨進程的互斥需求,還能支持同一個線程多次獲取鎖而不阻塞。記錄一下我對 MySQL 實現(xiàn)可重入鎖的思考:如何用 MySQL 實現(xiàn)可重入鎖?為什么實現(xiàn)過程中必須依賴事務(wù)?希望能解決你的一些疑惑。

一、先搞懂:什么是可重入鎖?

可重入鎖(也叫遞歸鎖)的核心特性是:同一個線程可以多次獲取同一把鎖,不會因為自己持有鎖而發(fā)生死鎖。

舉個例子:

  • 線程 A 先獲取鎖,執(zhí)行業(yè)務(wù)邏輯;
  • 業(yè)務(wù)邏輯中又調(diào)用了另一個需要同一把鎖的方法;
  • 線程 A 可以再次成功獲取鎖,而不會被阻塞;
  • 只有當(dāng)線程 A 釋放鎖的次數(shù)等于獲取鎖的次數(shù)時,鎖才會真正被釋放。

在 Java 里 ReentrantLock 就是典型的可重入鎖,而我們要做的,是用 MySQL 模擬出類似的效果。

二、MySQL 可重入鎖的基礎(chǔ):鎖表設(shè)計

要實現(xiàn)可重入鎖,我們需要一張表來記錄鎖的持有狀態(tài)、持有者和重入次數(shù):

CREATE TABLE `lock_table` (
  `id` INT AUTO_INCREMENT PRIMARY KEY,
  -- 鎖的唯一標(biāo)識
  `lock_name` VARCHAR(255) NOT NULL,
  -- 持有鎖的線程標(biāo)識
  `holder_thread` VARCHAR(255),
  -- 鎖的重入次數(shù),用于實現(xiàn)可重入性
  `reentry_count` INT DEFAULT 0
);

這張表的核心字段:

  • lock_name:鎖的唯一標(biāo)識,不同業(yè)務(wù)用不同的鎖名隔離。
  • holder_thread:標(biāo)記當(dāng)前哪個線程持有這把鎖。
  • reentry_count:記錄鎖被同一個線程重入的次數(shù),是實現(xiàn)可重入特性的關(guān)鍵。

三、核心實現(xiàn):加鎖與解鎖邏輯

1. 加鎖流程

1. 開啟事務(wù)
2. 執(zhí)行 SQL:
   SELECT holder_thread, reentry_count 
   FROM lock_table 
   WHERE lock_name = ? FOR UPDATE;
   - 若記錄不存在:執(zhí)行 INSERT INTO lock_table (lock_name, holder_thread, reentry_count) VALUES (?, ?, 1)
   - 若記錄存在且持有者是當(dāng)前線程:執(zhí)行 UPDATE lock_table SET reentry_count = reentry_count + 1 WHERE lock_name = ?
3. 提交事務(wù)

2. 解鎖流程

1. 開啟事務(wù)
2. 執(zhí)行 SQL:
   SELECT holder_thread, reentry_count 
   FROM lock_table 
   WHERE lock_name = ? FOR UPDATE;
   - 若記錄存在、持有者是當(dāng)前線程且重入次數(shù) > 1:執(zhí)行 UPDATE lock_table SET reentry_count = reentry_count - 1 WHERE lock_name = ?
   - 若記錄存在、持有者是當(dāng)前線程且重入次數(shù) ≤ 1:執(zhí)行 DELETE FROM lock_table WHERE lock_name = ?
3. 提交事務(wù)

四、靈魂拷問:為什么必須加事務(wù)?

很多同學(xué)會疑惑:“我不加事務(wù),直接執(zhí)行 SQL 不行嗎?” 答案是:不行。事務(wù)是 MySQL 可重入鎖的靈魂,沒有事務(wù),鎖的特性會直接失效。我們從三個維度拆解原因:

1. 控制鎖的生命周期:避免鎖提前釋放

MySQL InnoDB 默認 autocommit=1,單條 SQL 執(zhí)行完畢后會自動提交事務(wù)。

如果不加事務(wù):

  • 執(zhí)行 SELECT ... FOR UPDATE 時,會對目標(biāo)記錄加排他行鎖;
  • 這條 SQL 執(zhí)行完后,鎖會被自動釋放;
  • 后續(xù)的 INSERT/UPDATE 操作變成了全新的請求,需要重新競爭鎖。

這會導(dǎo)致:

  • 鎖根本沒有被 “持有”,其他線程可以在兩次操作之間搶占鎖;
  • 可重入特性直接失效,同一個線程第二次獲取鎖時可能被阻塞。

而事務(wù)的作用就是:在事務(wù)提交前,鎖不會被釋放。從查詢鎖狀態(tài)到完成寫入操作,整個過程中鎖都被當(dāng)前事務(wù)持有,保證了互斥性和可重入性。

2. 保證操作原子性:避免并發(fā)數(shù)據(jù)錯亂

加鎖 / 解鎖的核心邏輯是「查詢 → 判斷 → 寫入」,這三步必須是原子操作,否則在高并發(fā)場景下會出現(xiàn)數(shù)據(jù)錯亂。

舉個并發(fā)場景:

  • 線程 A 和線程 B 同時請求同一把鎖;
  • 線程 A 執(zhí)行 SELECT ... FOR UPDATE,發(fā)現(xiàn)記錄不存在,準(zhǔn)備 INSERT;
  • 線程 B 在 A 還沒 INSERT 之前,也執(zhí)行 SELECT ... FOR UPDATE,同樣發(fā)現(xiàn)記錄不存在;
  • 兩個線程都去 INSERT,要么觸發(fā)唯一鍵沖突,要么都插入成功,導(dǎo)致鎖被同時持有。

事務(wù)的原子性可以完美解決這個問題:

  • 把「查詢 → 判斷 → 寫入」封裝成一個不可分割的單元;
  • 要么全部成功,要么全部失??;
  • 只有當(dāng)前事務(wù)提交后,其他線程才能看到鎖的狀態(tài)變化,避免了競態(tài)條件。

3. 確??芍厝胝_性:重入次數(shù)的安全更新

可重入鎖的核心是維護 reentry_count 字段:

  • 加鎖時:如果是當(dāng)前線程持有鎖,reentry_count + 1;
  • 解鎖時:如果是當(dāng)前線程持有鎖,reentry_count - 1,次數(shù)為 0 時刪除鎖記錄。

如果不加事務(wù):

  • 線程 A 查詢到 reentry_count = 1,準(zhǔn)備執(zhí)行 +1;
  • 線程 B 可能在此時修改了 reentry_count,導(dǎo)致更新后的數(shù)據(jù)錯誤;
  • 最終鎖的狀態(tài)混亂,甚至出現(xiàn)鎖無法釋放的情況。

事務(wù)的隔離性保證了:

  • 在當(dāng)前事務(wù)更新 reentry_count 時,其他線程無法修改這條記錄;
  • 只有事務(wù)提交后,新的重入次數(shù)才會被持久化,其他線程才能感知到。

五、完整示例:

我們用偽代碼把加鎖和解鎖流程串起來,更直觀地感受事務(wù)的作用:

加鎖偽代碼

public boolean lock(String lockName, String threadName) {
    Connection conn = getConnection();
    try {
        // 1. 開啟事務(wù)
        conn.setAutoCommit(false);
        // 2. 查詢鎖狀態(tài)(加排他鎖)
        String querySql = "SELECT holder_thread, reentry_count FROM lock_table WHERE lock_name = ? FOR UPDATE";
        try (PreparedStatement ps = conn.prepareStatement(querySql)) {
            ps.setString(1, lockName);
            ResultSet rs = ps.executeQuery();
            if (rs.next()) {
                String holder = rs.getString("holder_thread");
                int count = rs.getInt("reentry_count");
                if (threadName.equals(holder)) {
                    // 可重入:重入次數(shù)+1
                    String updateSql = "UPDATE lock_table SET reentry_count = reentry_count + 1 WHERE lock_name = ?";
                    try (PreparedStatement updatePs = conn.prepareStatement(updateSql)) {
                        updatePs.setString(1, lockName);
                        updatePs.executeUpdate();
                    }
                } else {
                    // 被其他線程持有,獲取鎖失敗
                    conn.rollback();
                    return false;
                }
            } else {
                // 鎖不存在,直接加鎖
                String insertSql = "INSERT INTO lock_table (lock_name, holder_thread, reentry_count) VALUES (?, ?, 1)";
                try (PreparedStatement insertPs = conn.prepareStatement(insertSql)) {
                    insertPs.setString(1, lockName);
                    insertPs.setString(2, threadName);
                    insertPs.executeUpdate();
                }
            }
        }
        // 3. 提交事務(wù)
        conn.commit();
        return true;
    } catch (Exception e) {
        // 異?;貪L
        conn.rollback();
        return false;
    } finally {
        conn.setAutoCommit(true);
        closeConnection(conn);
    }
}

解鎖偽代碼

public boolean unlock(String lockName, String threadName) {
    Connection conn = getConnection();
    try {
        // 1. 開啟事務(wù)
        conn.setAutoCommit(false);
        // 2. 查詢鎖狀態(tài)
        String querySql = "SELECT holder_thread, reentry_count FROM lock_table WHERE lock_name = ? FOR UPDATE";
        try (PreparedStatement ps = conn.prepareStatement(querySql)) {
            ps.setString(1, lockName);
            ResultSet rs = ps.executeQuery();
            if (rs.next()) {
                String holder = rs.getString("holder_thread");
                int count = rs.getInt("reentry_count");
                if (!threadName.equals(holder)) {
                    // 不是鎖持有者,解鎖失敗
                    conn.rollback();
                    return false;
                }
                if (count > 1) {
                    // 重入次數(shù)>1,僅減1
                    String updateSql = "UPDATE lock_table SET reentry_count = reentry_count - 1 WHERE lock_name = ?";
                    try (PreparedStatement updatePs = conn.prepareStatement(updateSql)) {
                        updatePs.setString(1, lockName);
                        updatePs.executeUpdate();
                    }
                } else {
                    // 重入次數(shù)=1,刪除鎖記錄
                    String deleteSql = "DELETE FROM lock_table WHERE lock_name = ?";
                    try (PreparedStatement deletePs = conn.prepareStatement(deleteSql)) {
                        deletePs.setString(1, lockName);
                        deletePs.executeUpdate();
                    }
                }
            } else {
                // 鎖不存在,解鎖失敗
                conn.rollback();
                return false;
            }
        }
        // 3. 提交事務(wù)
        conn.commit();
        return true;
    } catch (Exception e) {
        conn.rollback();
        return false;
    } finally {
        conn.setAutoCommit(true);
        closeConnection(conn);
    }
}

六、總結(jié)

用 MySQL 實現(xiàn)可重入鎖,本質(zhì)是用數(shù)據(jù)庫表存儲鎖狀態(tài),用事務(wù)保證鎖的互斥性、原子性和生命周期。

事務(wù)的核心作用可以概括為三點:

  1. 鎖生命周期管理:事務(wù)提交前,鎖不會釋放,保證當(dāng)前線程持續(xù)持有鎖。
  2. 原子性保障:將「查詢 → 判斷 → 寫入」封裝為原子操作,避免并發(fā)數(shù)據(jù)錯亂。
  3. 可重入正確性:安全維護重入次數(shù),確保同一個線程可以多次獲取 / 釋放鎖。

這種實現(xiàn)方式雖然不如 Redis 等分布式鎖框架高效,但勝在簡單可靠,適合對性能要求不高、需要強一致性的場景,也能幫我們更好地理解事務(wù)和鎖的本質(zhì)。

以上就是MySQL實現(xiàn)可重入鎖的實踐指南的詳細內(nèi)容,更多關(guān)于MySQL可重入鎖實現(xiàn)的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • ubuntu系統(tǒng)中Mysql ERROR 1045 (28000): Access denied for user root@ localhost問題的解決方法

    ubuntu系統(tǒng)中Mysql ERROR 1045 (28000): Acces

    這篇文章主要介紹了ubuntu系統(tǒng)安裝mysql登陸提示 解決Mysql ERROR 1045 (28000): Access denied for user root@ localhost問題,需要的朋友可以參考下
    2017-05-05
  • mysql + nest.js  加鎖搞并發(fā)問題的實現(xiàn)

    mysql + nest.js  加鎖搞并發(fā)問題的實現(xiàn)

    本文主要介紹了使用NestJS和MySQL實現(xiàn)秒殺系統(tǒng)的庫存扣減,并解決了高并發(fā)超賣問題,通過原子SQL、NestJS原生SQL實現(xiàn)以及使用Redis和消息隊列等方案,可以有效提高系統(tǒng)的性能和穩(wěn)定性,
    2026-03-03
  • SQL函數(shù)實現(xiàn)Group_concat用法

    SQL函數(shù)實現(xiàn)Group_concat用法

    GROUP_CONCAT用于將每個分組內(nèi)的值連接成一個字符串,并通過指定的分隔符進行分隔,本文主要介紹了SQL函數(shù)實現(xiàn)Group_concat用法,具有一定的參考價值,感興趣的可以了解一下
    2024-08-08
  • my.cnf(my.ini)重要參數(shù)優(yōu)化配置說明

    my.cnf(my.ini)重要參數(shù)優(yōu)化配置說明

    本文針對mysql不同存儲引擎,MyISAM與Innodb進行了講解如何進行my.cnf(my.ini)的參數(shù)優(yōu)化
    2018-03-03
  • MySQL數(shù)據(jù)庫社招必考題:索引如何優(yōu)化WHERE子句?

    MySQL數(shù)據(jù)庫社招必考題:索引如何優(yōu)化WHERE子句?

    本文系統(tǒng)分享WHERE子句優(yōu)化方法,從定位低效SQL、分析原因到逐步改進,涵蓋索引、寫法調(diào)整及數(shù)據(jù)訪問優(yōu)化,通過實戰(zhàn)案例說明常見錯誤及解決策略,強調(diào)優(yōu)化需邏輯而非僅建索引,助力面試準(zhǔn)備
    2025-09-09
  • mysql事務(wù)處理用法與實例代碼詳解

    mysql事務(wù)處理用法與實例代碼詳解

    這篇文章主要介紹了mysql事務(wù)處理用法與實例代碼詳解,詳細的介紹了事物的特性和用法并實現(xiàn)php和mysql事務(wù)處理例子,非常具有實用價值,需要的朋友可以參考下
    2018-12-12
  • 解決mac使用homebrew安裝MySQL無法登陸問題

    解決mac使用homebrew安裝MySQL無法登陸問題

    如果你電腦是Mac的,使用homebrew安裝MySQL是一個非常便捷的方式,但是還是會出現(xiàn)一些問題。下面通過本文給大家介紹解決mac使用homebrew安裝MySQL無法登陸問題,需要的朋友可以參考下
    2017-03-03
  • Mysql如何將數(shù)據(jù)按照年月分組的統(tǒng)計

    Mysql如何將數(shù)據(jù)按照年月分組的統(tǒng)計

    這篇文章主要介紹了Mysql如何將數(shù)據(jù)按照年月分組的統(tǒng)計方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • Mysql中如何刪除某個字段的最后四個字符

    Mysql中如何刪除某個字段的最后四個字符

    這篇文章主要介紹了Mysql中如何刪除某個字段的最后四個字符,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • MySQL外鍵約束(FOREIGN KEY)的具體使用

    MySQL外鍵約束(FOREIGN KEY)的具體使用

    MySQL外鍵約束是表的一個特殊字段,經(jīng)常與主鍵約束一起使用,本文主要介紹了MySQL外鍵約束(FOREIGN KEY)的具體使用,具有一定的參考價值,感興趣的可以了解一下
    2024-05-05

最新評論

获嘉县| 东阿县| 拉萨市| 确山县| 涿鹿县| 化州市| 定远县| 普洱| 朝阳县| 晋宁县| 镇平县| 故城县| 南川市| 北川| 滨海县| 微山县| 襄汾县| 宁阳县| 湖州市| 昌平区| 溧阳市| 遂宁市| 藁城市| 津南区| 伊金霍洛旗| 定兴县| 洞口县| 乌鲁木齐县| 迁安市| 涟源市| 子洲县| 甘泉县| 晴隆县| 澄迈县| 辰溪县| 东宁县| 清流县| 双柏县| 延川县| 灌阳县| 自治县|