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

在 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 operationThread.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"; // 替换为实际数据库URLString 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.mzph.cn/bicheng/56888.shtml

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

相关文章

【GAMES101笔记速查——Lecture 16 Ray Tracing4】

上节课的内容&#xff1a;辐射度量学、光线传播、反射方程、渲染方程、全局光照、概率论复习 这节课要介绍一种真实的渲染方法-蒙特卡洛路径追踪 目录 1 简单回顾 1.1 渲染方程&#xff08;The Rendering Equation&#xff09; 1.2 概率 2 蒙特卡洛积分&#xff08;Monte…

SQL Injection | SQL 注入概述

关注这个漏洞的其他相关笔记&#xff1a;SQL 注入漏洞 - 学习手册-CSDN博客 0x01&#xff1a;SQL 注入漏洞介绍 SQL 注入就是指 Web 应用程序对用户输入数据的合法性没有判断&#xff0c;前端传入后端的参数是可控的&#xff0c;并且参数会带入到数据库中执行&#xff0c;导致…

(10) GTest c++单元测试(mac版)

文章目录 概要安装实现机制-断言&#xff08;简单、独立的测试&#xff09;实现机制-测试套件实现机制-Test Fixture和事件 概要 官方文档 https://google.github.io/googletest/ 安装 git clone https://github.com/google/googletestcd googletestmkdir build && c…

数字化营销助企业在生态平台实现内卷突围

在当今数字化时代&#xff0c;企业竞争激烈&#xff0c;内卷化严重。而数字化生态平台建设与数字化营销为企业带来了新机遇。 数字化生态平台意义重大。它能整合企业内外资源&#xff0c;提高运营效率。打破地域限制&#xff0c;拓展市场&#xff0c;吸引更多客户。还能为企业创…

【AI副业项目】太离谱了!爆涨粉47W+,下一个风口项目AI+大健康养S赛道,单月变现30W,教你如何用AI做爆款健康养生账号

我一直说小红薯平台是最适合新手素人做的平台&#xff0c;去中心化的平台&#xff0c;任何普通人都可以在这个平台分一杯羹的平台。 但但但是很多朋友发小红薯作品都是超低的小眼睛&#xff0c;连最基本的流量都没拿到。 从他们的经历来看就是小红薯太难做了。那是没有掌握技…

【Vue】Vue3.0 (十二)、watchEffect 和watch的区别及使用

上篇文章&#xff1a; 【Vue】Vue3.0 &#xff08;十二&#xff09;、watch对ref定义的基本类型、对象类型&#xff1b;reactive定义的对象类型的监视使用 &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Vue专栏&#xff1a;点击&#xff01; ⏰️创作时间&…

智慧油田智能安全管控方案-AI助力油气田安全管控升级

在科技日新月异的今天&#xff0c;万物纵横科技凭借其前沿的智慧油田智能安全管控方案&#xff0c;正引领着油气田行业向智能化、高效化转型。该方案深度融合了AI视频智能分析与AIoT&#xff08;物联网人工智能&#xff09;技术&#xff0c;为采油场、油气场的设备运维、环境监…

2024全网最详细CTF入门指南、CTF夺旗赛使用工具及刷题网站

2024年最新的CTF&#xff08;Capture The Flag&#xff0c;夺旗赛&#xff09;入门指南如下&#xff0c;涵盖了入门思路、常见题型及练习网站推荐&#xff0c;帮助你逐步了解并提升在CTF中的解题技巧。 如果你对网络安全入门感兴趣&#xff0c;我给大家整理好了相关资料&#…

Java集合剖析2】Java集合底层常用数据结构

一、数据结构与集合 接下来就要学习集合具体的实现类了&#xff0c;集合的实现类底层可能用1种或多种数据结构来存储数据。所以在学习集合的实现类前&#xff0c;我们有必要了解一下一些常见的数据结构&#xff0c;这样我们在后面查看集合实现类的底层源码时&#xff0c;才不会…

项目模块三:Socket模块

一、模块设计 1、套接字编程常用头文件展示 #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <fcntl.h> 2、成员函数设计 &#xff08;1&#xf…

题目:连续子序列

解题思路&#xff1a; 首先&#xff0c;不能使用暴力枚举&#xff0c;时间为O(n2)&#xff0c;超时。以下为正确做法&#xff1a; 假设找到一段区间&#xff08;其和>m&#xff09;&#xff0c;如上图黄色部分&#xff0c;那么该区间加上i后面的元素形成的新区间和都>m&a…

Excel中如何进行傅里叶变换(FT),几步完成

在 Excel 中&#xff0c;虽然没有像 MATLAB 那样专门的函数库来直接进行傅里叶变换&#xff0c;但可以使用 Excel 内置的分析工具库提供的傅里叶变换&#xff08;FT &#xff0c;Fourier Transform&#xff09;功能。这个工具可以对数据进行频域分析。以下是如何在 Excel 中进行…

【.net core使用minio大文件分片上传】.net core使用minio大文件分片上传以及断点续传、秒传思路

版本&#xff1a;.net core 7 需求&#xff1a;net限制了上传的大小&#xff0c;只能上传25M上下的文件&#xff0c;如果上传一个八十多兆的文件&#xff0c;swagger接口报错&#xff0c;如果前端调用上传接口&#xff0c;会报CORS跨域错误&#xff0c;这篇文章介绍怎么使用分片…

C#学习笔记(九)

C#学习笔记&#xff08;九&#xff09; 第六章 面向对象编程&#xff08;一&#xff09;类与对象、字段与属性一、类与对象正确的理解1. 什么是类&#xff1f;2.什么是对象&#xff1f;3. 类与对象的区别 二、类的基本规范和对象使用1. 类的规范 三、类的访问修饰符&#xff08…

GoFly快速开发框架的utils-plugin扩展包开发演示教程

说明 本插件是教大家如何开发框架utils->plugin下扩展插件包&#xff0c;在开发时可安装本插件&#xff0c;参考本插件代码结构写你插件&#xff0c;这样可以达到规范插件代码&#xff0c;同时也也是为了兼容你安装其他人在代码仓通过扩展插件包&#xff0c;如果不规范可能…

Bolt 一款AI 全栈 Web 在线开发工具

参考&#xff1a; https://bolt.new/ github项目也可以支持Bolt在线打开编辑 直接连接前输入&#xff0c;比如 https://github.com/lyz1810/live2dSpeek更换成 https://bolt.new/github.com/lyz1810/live2dSpeek https://bolt.new/github.com/oh-my-live2d/oh-my-live2d 主要偏…

eUSB2规范介绍

一、说明 eUSB的全称是Embedded USB2,也支持低速、全速和高速三个速率,不过降低了电压幅值,从而使SOC更小、功耗更低,主要应用在芯片间互联,如需应用在设备间传输,一般需要加中继器。 eUSB2还支持USB2.0协议,仅物理层要求不同。 eUSB2支持2个模式:原生模式(native m…

[C#][winform]基于yolov8的道路交通事故检测系统C#源码+onnx模型+评估指标曲线+精美GUI界面

【重要说明】 该系统以opencvsharp作图像处理,onnxruntime做推理引擎&#xff0c;使用CPU进行推理&#xff0c;适合有显卡或者没有显卡windows x64系统均可&#xff0c;不支持macOS和Linux系统&#xff0c;不支持x86的windows操作系统。由于采用CPU推理&#xff0c;要比GPU慢。…

Turn-it:调整它:优化线材重构雕塑制造

&#x1f428;文章摘要abstract 电线雕塑在工业应用和日常生活中都很重要。 本文提出了一种新的制造策略&#xff0c;通过调整目标形状以适应电线弯曲机&#xff0c;然后由人工将其弯曲回目标形状。&#xff08;机器弯曲人工弯曲&#xff09; 该方法通过两阶段弯曲策略实现&a…

王爽汇编语言第三版实验2

实验任务 (1)使用Debug&#xff0c;将下面的程序段写 入程序&#xff0c;逐条进行&#xff0c;根据指令执行后的实际运行情况填空。 mov ax,ffff mov ds,ax mov ax,2200 mov ss,ax mov sp,0100 mov ax,[0] ;axC0EAH add ax,[2] ;axCOFCH mov bx,[4] ;bx30F0H add …