如果用Java设计MySQL中表级锁、行级锁和间歇锁会是怎么的?

news2024/10/22 22:20:15

在 MySQL 中,锁机制是确保数据一致性和并发控制的重要手段。MySQL 支持多种锁类型,包括表级锁、行级锁等,每种锁的适用场景、影响范围和实现机制各不相同。我们将逐一介绍它们,并通过模拟代码展示不同锁的实现。

1. 锁类型及其影响范围

1.1 表级锁(Table Lock)

  • 范围:锁定整个表,其他事务不能对表进行任何修改。

  • 使用场景

    • ALTER TABLEDROP TABLE 等 DDL 操作。
    • 全表扫描查询需要保证一致性时,如备份或批量数据导入。
  • 锁定失败的情况:如果另一个事务已经持有锁(读/写锁),则需要等待或报错,具体取决于是否允许等待。

表级锁指令示例:
LOCK TABLES table_name WRITE; -- 写锁,其他事务无法读写
UNLOCK TABLES; -- 解锁

1.2 行级锁(Row Lock)

  • 范围:锁定特定的行,不影响其他行的操作。

  • 使用场景

    • 高并发环境中,多个事务可以同时操作不同的行。
    • 适用于需要频繁读写操作的场景。
  • 锁定失败的情况:在高并发环境中,如果两个事务试图同时更新同一行,会发生锁等待,最终可能出现死锁

行级锁指令示例:
SELECT * FROM table_name WHERE id=1 FOR UPDATE; -- 行锁,锁定特定行

1.3 意向锁(Intention Lock)

  • 范围:一种表级锁的扩展,用于行级锁的意图声明。不会阻塞其他事务对不同行的操作,但会声明事务对特定行的操作意图。

  • 使用场景

    • InnoDB 引擎自动实现,不需要显式声明。
    • 用于协调行级锁和表级锁之间的冲突。
  • 锁定失败的情况:当表锁与行锁发生冲突时,意向锁会协调操作,避免事务死锁。

1.4 间隙锁(Gap Lock)

  • 范围:锁定行之间的“间隙”,防止插入操作。

  • 使用场景

    • 避免“幻读”,适用于REPEATABLE READ隔离级别。
  • 锁定失败的情况:如果有其他事务试图在锁定范围内插入新行,插入会被阻塞或失败。

间隙锁指令示例:
SELECT * FROM table_name WHERE id > 10 FOR UPDATE; -- 间隙锁,锁定 id > 10 的范围,禁止插入

2. 不同锁的设计实现逻辑模拟

2.1 表级锁模拟(Java 示例)

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Table {
    private final Lock tableLock = new ReentrantLock();

    public void lockTable(String threadName) {
        tableLock.lock();
        try {
            System.out.println(threadName + " has locked the table");
            // Simulate table operation
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            tableLock.unlock();
            System.out.println(threadName + " has unlocked the table");
        }
    }
}

public class TableLockSimulation {
    public static void main(String[] args) {
        Table table = new Table();

        new Thread(() -> table.lockTable("Thread 1")).start();
        new Thread(() -> table.lockTable("Thread 2")).start();
    }
}

2.2 行级锁模拟(Java 示例)

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.HashMap;
import java.util.Map;

class InnoDBTable {
    private final Map<Integer, Lock> rowLocks = new HashMap<>(); // 每行一个锁

    public InnoDBTable(int rowCount) {
        // 初始化行锁
        for (int i = 0; i < rowCount; i++) {
            rowLocks.put(i, new ReentrantLock());
        }
    }

    // 模拟行级锁的查询操作
    public void accessRow(int rowId, String threadName) {
        Lock rowLock = rowLocks.get(rowId);
        if (rowLock != null) {
            rowLock.lock(); // 锁定行
            try {
                System.out.println(threadName + " acquired row-level lock on row " + rowId);
                // 模拟业务操作
                Thread.sleep(1000);
                System.out.println(threadName + " finished working on row " + rowId);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                rowLock.unlock(); // 释放行锁
            }
        }
    }
}

public class InnoDBLockSimulation {
    public static void main(String[] args) {
        InnoDBTable table = new InnoDBTable(10); // 10 行数据

        // 启动线程,模拟多个事务操作不同的行
        new Thread(() -> table.accessRow(5, "Thread 1")).start();
        new Thread(() -> table.accessRow(5, "Thread 2")).start();
        new Thread(() -> table.accessRow(8, "Thread 3")).start();
    }
}

2.3 间隙锁模拟(Java 示例)

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;

class GapLockSimulation {
    private final Lock lock = new ReentrantLock();
    private final Condition gapEmpty = lock.newCondition();

    private boolean hasGapRecord = false; // 表示间隙是否已有记录

    // 模拟插入操作
    public void insertRecord(String threadName) throws InterruptedException {
        lock.lock();
        try {
            while (hasGapRecord) {
                System.out.println(threadName + " waiting due to gap lock.");
                gapEmpty.await(); // 等待间隙解锁
            }
            // 模拟插入
            hasGapRecord = true;
            System.out.println(threadName + " inserted record in gap.");
        } finally {
            lock.unlock();
        }
    }

    // 模拟释放间隙锁
    public void releaseGap(String threadName) {
        lock.lock();
        try {
            hasGapRecord = false;
            gapEmpty.signalAll(); // 通知所有等待的线程
            System.out.println(threadName + " released gap lock.");
        } finally {
            lock.unlock();
        }
    }
}

public class GapLockDemo {
    public static void main(String[] args) {
        GapLockSimulation gapLock = new GapLockSimulation();

        new Thread(() -> {
            try {
                gapLock.insertRecord("Thread 1");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                gapLock.insertRecord("Thread 2");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                Thread.sleep(3000);
                gapLock.releaseGap("Thread 1");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

3. 锁定失败的情况

锁定失败通常发生在两种情况下:

  1. 锁冲突:一个事务已经持有锁,另一个事务需要等待,或者在某些情况下,可能会报错。
  2. 死锁:当两个事务循环依赖彼此的锁时,数据库会检测到死锁并回滚其中一个事务以打破死锁。

为了模拟锁冲突,我们可以通过创建两个线程在同一个数据库表上进行相互冲突的操作,来展示锁冲突的场景。具体来说,假设我们有一个事务在一个线程中锁定了一行数据,并且执行了更新操作,而另一个事务也试图在相同的行上执行更新操作。由于第一个事务还没有提交,第二个事务会发生锁冲突,必须等待第一个事务释放锁。

场景说明

  1. 事务1:先获取行级锁,执行更新操作,不立即提交事务。
  2. 事务2:尝试获取相同的行级锁,由于事务1还没有提交,事务2将被阻塞直到事务1提交。

模拟锁冲突的 Java 代码示例

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class LockConflictDemo {

    public static void main(String[] args) {
        // 启动两个线程模拟两个事务
        Thread transaction1 = new Thread(() -> {
            try {
                simulateTransaction1();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        });

        Thread transaction2 = new Thread(() -> {
            try {
                simulateTransaction2();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        });

        transaction1.start();
        try {
            // 确保事务1先启动并锁定行
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        transaction2.start();
    }

    private static void simulateTransaction1() throws SQLException {
        Connection connection = null;
        try {
            connection = getConnection();
            connection.setAutoCommit(false); // 开启事务

            String sql = "UPDATE test_table SET value = ? WHERE id = ?";
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, "Transaction1");
            preparedStatement.setInt(2, 1); // 假设锁定id为1的行
            preparedStatement.executeUpdate();

            System.out.println("Transaction 1: Row locked, holding lock for 10 seconds...");

            // 模拟长时间持有锁,不提交
            Thread.sleep(10000);

            connection.commit(); // 提交事务
            System.out.println("Transaction 1: Committed.");
        } catch (Exception e) {
            e.printStackTrace();
            if (connection != null) {
                connection.rollback(); // 回滚事务
            }
        } finally {
            if (connection != null) {
                connection.close(); // 关闭连接
            }
        }
    }

    private static void simulateTransaction2() throws SQLException {
        Connection connection = null;
        try {
            connection = getConnection();
            connection.setAutoCommit(false); // 开启事务

            String sql = "UPDATE test_table SET value = ? WHERE id = ?";
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, "Transaction2");
            preparedStatement.setInt(2, 1); // 同样尝试锁定id为1的行
            preparedStatement.executeUpdate();

            System.out.println("Transaction 2: Row locked successfully.");

            connection.commit(); // 提交事务
            System.out.println("Transaction 2: Committed.");
        } catch (Exception e) {
            e.printStackTrace();
            if (connection != null) {
                connection.rollback(); // 回滚事务
            }
        } finally {
            if (connection != null) {
                connection.close(); // 关闭连接
            }
        }
    }

    private static Connection getConnection() throws SQLException {
        String url = "jdbc:mysql://localhost:3306/testdb"; // 替换为实际数据库URL
        String user = "root"; // 替换为实际用户名
        String password = "password"; // 替换为实际密码
        return DriverManager.getConnection(url, user, password);
    }
}

代码解析

  1. simulateTransaction1

    • 开启事务,更新 test_table 表中的 id = 1 行并持有锁不提交。
    • 持有锁 10 秒钟,模拟长时间占用锁。
  2. simulateTransaction2

    • 在事务1未提交的情况下,尝试更新同一行的数据。
    • 由于事务1尚未提交,事务2会被锁定等待直到事务1释放锁。

模拟结果

  1. 事务1 会首先锁定 id = 1 的行,并在持有锁的 10 秒钟内执行更新操作,但不提交事务。
  2. 事务2 在事务1未提交前,尝试获取锁进行更新,会因为锁冲突被阻塞,直到事务1释放锁(即提交或回滚事务)。
  3. 当事务1释放锁后,事务2才会获取到锁,并执行更新操作。

运行效果

  • 当你运行这段代码时,控制台会先输出Transaction 1: Row locked, holding lock for 10 seconds...,然后 10 秒钟后,Transaction 1 提交。
  • 此时,Transaction 2 才会获得锁,进行更新并提交。

锁冲突的场景与解决方法

  1. 场景:多个事务并发访问同一行数据时,事务间的冲突会导致等待或阻塞。
  2. 解决方法
    • 减少事务持有锁的时间:避免长时间占用锁,尽快提交事务。
    • 优化锁策略:通过使用行锁代替表锁,减少锁冲突的概率。
    • 锁等待超时:设置锁等待时间,避免无限等待。

这种锁冲突的场景非常常见于高并发的数据库应用程序中,因此了解如何控制和优化锁机制是提高系统并发性能的关键之一。

4. 总结与优化场景

  • 表级锁:适合批量操作或结构修改时,需谨慎使用以避免阻塞过多操作。
  • 行级锁:适合高并发环境,提升并发操作性能。
  • 间隙锁:适合防止幻读,尤其在事务隔离级别较高时使用。

通过这些锁机制,MySQL 能够在不同的并发场景中灵活管理数据一致性与性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2221101.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

红日靶场(三)1、环境介绍及环境搭建

1、靶场介绍 红日靶场03是一个用于安全测试和渗透测试的虚拟化环境&#xff0c;可以帮助用户通过模拟攻击和防御场景来提升网络安全技能。该靶场包含了多个虚拟机和网络配置&#xff0c;用户可以在其中进行各种安全测试活动&#xff0c;如信息收集、漏洞利用、权限提升等。 2…

Java项目-基于Springboot的高校党务系统项目(源码+说明).zip

作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 开发运行环境 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/…

ACL访问控制

要求&#xff1a; PC1与PC2不能通信。PC1可以和PC3通信。PC2可以和PC3通信。 1. VLAN配置 根据拓扑图的连接&#xff0c;PC1、PC2、PC3属于不同的VLAN。我们需要确保交换机上的端口已经正确划分到不同的VLAN。假设交换机接口的VLAN配置已经完成&#xff08;其他博文有)&…

在VMware中安装LInux

1.打开VMware选择新建虚拟机 2.这里建议初学者选择“典型&#xff08;推荐&#xff09;”。 3.已提前准备好 Linux 系统的映像文件&#xff08;.iso 文件&#xff09;&#xff0c;此处可选择“安装程序光盘映像文件”&#xff0c;并通过“浏览”按钮找到要安装 Linux 系统的 i…

“金秋敬老月 浓浓孝老情”2024中益孝行大爱进万家敬老月公益活动走进涿州

为贯彻实施积极应对人口老龄化国家战略&#xff0c;弘扬中华民族孝亲敬老传统美德&#xff0c;10月22日&#xff0c;中益老龄事业发展中心和涿州市东城坊镇人民政府在安享城养老院联合开展“金秋敬老月&#xff0c;浓浓孝老情”2024中益孝行大爱进万家敬老月公益活动。中益老龄…

android openGL ES详解——混合

一、混合概念 混合是一种常用的技巧&#xff0c;通常可以用来实现半透明。但其实它也是十分灵活的&#xff0c;你可以通过不同的设置得到不同的混合结果&#xff0c;产生一些有趣或者奇怪的图象。混合是什么呢&#xff1f;混合就是把两种颜色混在一起。具体一点&#xff0c;就…

《Python游戏编程入门》注-第2章2

《Python游戏编程入门》的“2.2.5 绘制线条”中提到了通过pygame库绘制线条的方法。 1 相关函数介绍 通过pygame.draw模块中的line()函数来绘制线条&#xff0c;该函数的格式如下所示。 line(surface, color, start_pos, end_pos, width1) -> Rect 其中&#xff0c;第一…

面试题:Redis(八)

1. 面试题 2. 锁的特性 单机版同一个jvm虚拟机内&#xff0c;synchronized或者Lock接口 分布式多个不同jvm虚拟机&#xff0c;单机的线程锁机制不再起作用&#xff0c;资源类在不同的服务器之间共享 一个靠谱分布式锁所需的条件 3. 手写分布式锁 3.1 独占性&#xff08;线程安…

VScode运行C语言终端输出中文乱码问题解决方案

VScode运行C语言输出中文乱码问题解决方案 由于 VSCode 的终端是对系统的 cmd 命令行工具的调用&#xff0c;而 cmd 的默认编码为 GBK。当我们在 VSCode 中以 UTF-8 编码进行代码编写且代码里含有中文字符时&#xff0c;在终端运行代码便会出现中文乱码现象。要解决此问题&…

传统企业应该如何突破管理瓶颈?

传统企业应该如何突破管理瓶颈&#xff1f; 【导读】 作为传统企业&#xff0c;有很多传承的传统机制&#xff0c;然而在市场机制下&#xff0c;越来越能够深刻感受到外部市场变化快的特点&#xff0c;在逐步适应以市场为导向的环境下&#xff0c;传统企业自身如何做好管理工…

C/C++每日一练:实现一个环形队列

队列&#xff08;queue&#xff09; 队列是一种先进先出&#xff08;FIFO&#xff0c;First In First Out&#xff09; 的数据结构&#xff0c;类似于排队的场景。最先进入队列的元素最先被处理&#xff0c;而后加入的元素则排在队列的末尾。 常见的队列操作&#xff1a; 入队…

【linux 多进程并发】0301 Linux创建后台服务进程,daemon进程,自己的进程可以被一号进程接管啦

0301 Linux创建后台进程 ​专栏内容&#xff1a; postgresql使用入门基础手写数据库toadb并发编程 个人主页&#xff1a;我的主页 管理社区&#xff1a;开源数据库 座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物. 一、概述…

Matlab实现鲸鱼优化算法(WOA)求解路径规划问题

目录 1.内容介绍 2.部分代码 3.实验结果 4.内容获取 1内容介绍 鲸鱼优化算法&#xff08;WOA&#xff09;是一种受自然界座头鲸捕食行为启发的优化算法&#xff0c;它通过模拟座头鲸的环绕猎物、螺旋游动和搜索猎物三种主要行为来探索和优化问题的解。WOA因其强大的全局搜索能…

RabbitMQ最新版本4.0.2在Windows下的安装及使用

RabbitMQ 是一个开源的消息代理和队列服务器&#xff0c;提供可靠的消息传递和队列服务。它支持多种消息协议&#xff0c;包括 AMQP、STOMP、MQTT 等。本文将详细介绍如何在 Windows 系统上安装和使用最新版本的 RabbitMQ 4.0.2。 前言 RabbitMQ 是用 Erlang 语言开发的 AMQP&…

攻坚金融关键业务系统,OceanBase亮相2024金融科技大会

10月15-16日&#xff0c;第六届中新数字金融应用博览会与2024金融科技大会&#xff08;简称“金博会”&#xff09;在苏州工业园区联合举办。此次大会融合了国家级重要金融科技资源——“中国金融科技大会”&#xff0c;围绕“赋能金融高质量发展&#xff0c;金融科技创新前行”…

如何实现金蝶商品数据集成到电商系统的SKU

如何实现金蝶商品数据集成到电商SKU系统 金蝶商品数据集成到电商SKU的技术实现 在现代企业的数据管理中&#xff0c;系统间的数据对接与集成是提升业务效率和准确性的关键环节。本文将分享一个实际案例&#xff1a;如何通过轻易云数据集成平台&#xff0c;将金蝶云星辰V2中的商…

Gin框架操作指南06:POST绑定(下)

官方文档地址&#xff08;中文&#xff09;&#xff1a;https://gin-gonic.com/zh-cn/docs/ 注&#xff1a;本教程采用工作区机制&#xff0c;所以一个项目下载了Gin框架&#xff0c;其余项目就无需重复下载&#xff0c;想了解的读者可阅读第一节&#xff1a;Gin操作指南&#…

idea删除git历史提交记录

前言&#xff1a;此文章是我在实际工作中有效解决问题的方法&#xff0c;做记录的同时也供大家参考&#xff01; 一、 首先&#xff0c;通过idea的终端或系统的cmd控制台&#xff0c;进入到你的项目文件根目录&#xff0c;idea终端默认就是项目根目录。 二、确保你当前处于要删…

浙大恩特CRM Quotegask_editAction SQL注入漏洞复现

0x01 产品描述&#xff1a; 浙大恩特CRM是由浙江大学恩智浙大科技有限公司推出的客户关系管理&#xff08;CRM&#xff09;系统。该系统旨在帮助企业高效管理客户关系&#xff0c;提升销售业绩&#xff0c;促进市场营销和客户服务的优化。 0x02 漏洞描述&#xff1a; 浙大恩特…