如果用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…

ubuntu 24.04 下载安装离线包,ubuntu 24.04 配置xrdp

ubuntu 24.04 配置xrdp 1. 安装gnome-tweaks sudo apt install gnome-tweaks 2. 配置 cat <<EOF > ~/.xsessionrc export GNOME_SHELL_SESSION_MODEubuntu export XDG_CURRENT_DESKTOPubuntu:GNOME export XDG_CONFIG_DIRS/etc/xdg/xdg-ubuntu:/etc/xdg EOF 3.…

使用poi-tl动态写入目录更新问题解决

在使用poi-tl动态写完word后&#xff0c;是无法更新目录的&#xff0c;使用poi-tl提供的插件也是不行的&#xff0c;而且很多使用poi手动写入的也是不行&#xff0c;最多就是让你在打开文件时提示你更新目录/更新域&#xff0c;用户体验很差&#xff0c;要点击好几次而且wps还不…

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…

JavaScript 了解专用工作者线程

目录 一、何为专用工作者线程二、专用工作者线程的全局对象三、创建专用工作者线程的方法四、专用工作者线程特点END 一、何为专用工作者线程 最简单的WEB工作者线程,执行在页面加载外的其他任意如网络请求,繁杂计算操作并可以与页面通信. 二、专用工作者线程的全局对象 专用…

十、结构型(外观模式)

外观模式&#xff08;Facade Pattern&#xff09; 概念 外观模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;旨在为复杂子系统提供一个简化的统一接口。通过外观模式&#xff0c;客户端可以与子系统交互&#xff0c;而无需了解子系统的内部复杂性…

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

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

AdGuard v4.7.61 拦截所有广告 解锁高级版

AdGuard v4.7.61 拦截所有广告 解锁高级版 下载链接&#xff1a;夸克网盘分享

三、MyBatis实践(3):多表映射,动态语句,高级扩展

三、MyBatis多表映射 3.1 多表映射概念 多表查询结果映射思路 上面课程中&#xff0c;我全面讲解了单表的mybatis操作&#xff01;但是开发中更多的是多表查询需求&#xff0c;这种情况我们如何让进行处理&#xff1f; MyBatis 思想是&#xff1a;数据库不可能永远是你所想或所…

【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;为采油场、油气场的设备运维、环境监…

两个字符串的最长 公共子序列

给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符的相对顺序的情况下删除某些字符&#xff08;也可以…

Unity Apple Vision Pro 自定义手势识别交互

Vision Pro 是可以使用Unity 提供的XR Hand SDK,可通过XR Hand制作自定义手势识别&#xff0c;通过识别出不同的手势做自定义交互 效果预览 在VisionPro中看VisionPro|手势交互|自定义手势识别 Unity Vision Pro 中文课堂教程地址&#xff1a; Unity3D Vision Pro 开发教程【…

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

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

vmware ubuntu根分区扩容

从你的 fdisk -l 输出来看&#xff0c;系统的 /dev/sda 是你的主要磁盘&#xff0c;大小为 80 GB&#xff0c;分为三个分区&#xff1a; /dev/sda1&#xff1a;1 MB 的 BIOS 引导分区/dev/sda2&#xff1a;2 GB 的 Linux 文件系统分区/dev/sda3&#xff1a;78 GB 的 Linux 文件…

Unity3D ScrollView 滚动视图组件详解及代码实现

在Unity3D中&#xff0c;ScrollView&#xff08;滚动视图&#xff09;是一种常用的UI组件&#xff0c;它允许用户通过滚动来查看超出当前视图范围的内容。ScrollView通常用于显示长列表、大量文本或图像等。本文将详细介绍Unity3D中的ScrollView组件&#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…