Blage's Coding Blage's Coding
Home
算法
  • 手写Spring
  • SSM
  • SpringBoot
  • JavaWeb
  • JAVA基础
  • 容器
  • Netty

    • IO模型
    • Netty初级
    • Netty原理
  • JVM
  • JUC
  • Redis基础
  • 源码分析
  • 实战应用
  • 单机缓存
  • MySQL

    • 基础部分
    • 实战与处理方案
    • 面试
  • ORM框架

    • Mybatis
    • Mybatis_Plus
  • SpringCloudAlibaba
  • MQ消息队列
  • Nginx
  • Elasticsearch
  • Gateway
  • Xxl-job
  • Feign
  • Eureka
  • 面试
  • 工具
  • 项目
  • 关于
🌏本站
🧸GitHub (opens new window)
Home
算法
  • 手写Spring
  • SSM
  • SpringBoot
  • JavaWeb
  • JAVA基础
  • 容器
  • Netty

    • IO模型
    • Netty初级
    • Netty原理
  • JVM
  • JUC
  • Redis基础
  • 源码分析
  • 实战应用
  • 单机缓存
  • MySQL

    • 基础部分
    • 实战与处理方案
    • 面试
  • ORM框架

    • Mybatis
    • Mybatis_Plus
  • SpringCloudAlibaba
  • MQ消息队列
  • Nginx
  • Elasticsearch
  • Gateway
  • Xxl-job
  • Feign
  • Eureka
  • 面试
  • 工具
  • 项目
  • 关于
🌏本站
🧸GitHub (opens new window)
  • MySQL

    • 基础部分

      • 初识MySQL
      • 基础架构&日志
      • 事务隔离
      • 全局锁、表锁、行锁
        • 1.全局锁
        • 2.表级锁
          • 2.1表锁
          • 2.2元数据锁
          • 小表添加字段的安全问题
          • 添加字段如何安全解决
          • 2.3场景分析
        • 3.行锁
          • 3.1两阶段锁协议
          • 3.2死锁
          • 3.3死锁解决方式
          • 3.4死锁监测机制带来的热点数据更新的性能问题解决方式
      • 事务的隔离性和行锁
      • 索引
      • 索引深入(中)
      • 内存脏页刷盘
      • 数据库表的空间回收
      • count
      • order by
      • SQL语句性能差异分析
      • 幻读与间隙锁
      • 加锁规则案例分析
      • binlog和redolog如何写入磁盘
      • MySQL一致性与高可用性
      • kill命令
      • 全表扫描与内存占用
      • join
      • 临时表与内存表
      • 自增主键
      • insert操作加锁场景分析
      • grant
      • 分区表
      • 思维导图
    • 实战与处理方案

    • 面试

  • ORM框架

  • 数据库
  • MySQL
  • 基础部分
phan
2023-06-09
目录

全局锁、表锁、行锁

# 全局锁、表锁、行锁

# 1.全局锁

MySQL加全局读锁:Flush Table With Read Lock 此时装个库处于只读状态,所有更新修改语句全部会被阻塞。

全局锁使用场景:全库逻辑备份。但是为了不影响业务,可以在可重复隔离级别下开启事务,拿到一致性视图进行备份更新。一般有如下几种方法:

  • 使用single-transaction启动事务。(InnoDB支持)
  • 如果当前数据库表不支持事务,采用set global readonly=true。但是同时有以下缺点:
    • 改变全局变量影响大
    • 用户如果异常断开连接,那么数据库会一直保存readonly状态,导致整个库处于不可写状态。

# 2.表级锁

# 2.1表锁

lock tables t1 read,t2 write:限制当前和其它线程只能够对t1表进行读,当前线程只能够对t2写,其他线程对t2读写都会被阻塞。通过unlock tables释放锁。

# 2.2元数据锁

MDL在访问每个表时会自动加锁(主要是用于对表结构的更改),系统会默认添加的锁。对表做增删改查时,加MDL读锁;对表结构进行变更操作时,添加MDL写锁。事务提交才会释放锁。

MDL读锁之间不互斥,读写锁互斥。

# 小表添加字段的安全问题

  • A线程获取“MDL读锁”,对数据库进行增删改查
  • B线程要获取“MDL写锁”,给表添加新的字段。但是由于A线程没有提交事务,因此B线程被阻塞。
  • C线程要获取“MDL读锁”,但是因为要读写的内容是B添加后的新表,因此B被阻塞会导致C以及后续所有读写请求都会被阻塞。整个表此时完全不可读写。

# 添加字段如何安全解决

  • 本质上是长时间占据事务不提交的问题,在添加字段之前查询innodb_trx表,查看所要添加字段的那个表是否在执行长事务,如果是则暂停当前添加操作或者是kill掉长事务。
  • 如果该表是热点信息表,虽然不占据长事务但是频繁被其它线程申请读锁。这种情况下如果有字段添加操作,那么需要给这个alter操作设置一个等待时间,在等待时间内如果能拿到MDL写锁则正常进行DDL,超时后则放弃当前DDL操作,避免阻塞后续的增删改查操作。

# 2.3场景分析

当前线程使用single-transaction添加读锁备份数据,主库从binlog传来一个DDL语句,最后Q5读到的结果有什么不同?

Q1:SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; 设置可重复读隔离级别

Q2:START TRANSACTION  WITH CONSISTENT SNAPSHOT; 获取一致性视图
/* other tables */
Q3:SAVEPOINT sp; 设置保存点
/* 时刻 1 */
Q4:show create table `t1`; 拿到表结构
/* 时刻 2 */
Q5:SELECT * FROM `t1`; 导出数据
/* 时刻 3 */
Q6:ROLLBACK TO SAVEPOINT sp; 释放MDL读锁,并回滚到sp
/* 时刻 4 */
1
2
3
4
5
6
7
8
9
10
11
12
  • DDL在时刻1到达,则Q5没有影响照常运行,导出的是DDL修改过后的表结构。
  • DDL在时刻2到达,会占据MDL写锁,在执行Q5时会报错Table definition has changed, please retry transaction,mysqldump终止。
  • DDL如果是在时刻3到达,那么因为Q5占据着MDL读锁,则DDL会被阻塞,直到Q6完成
  • DDL在时刻4开始,因为Q6已经释放MDL读锁了,因此没有影响,导出的数据是DDL前的表结构

# 3.行锁

InnoDB支持行锁;而MyISAM不支持,因此并发控制只能够使用表锁,粒度太大。

💡加行锁是由InnoDB引擎完成,与server层无关。

# 3.1两阶段锁协议

行锁的获取时机:两个事务操作同一行数据,存在冲突时才会加行锁,先操作的事务会获得行锁。

行锁的释放:获取行锁的事务直到提交完事务,才会释放行锁。

以下图为例,事务A拿到行锁,因此B执行时会被阻塞,直到事务A释放锁之后,事务B才执行。

因此如果在一个事务中,需要操作多行数据,每个增删改查语句都会添加行锁,因此可以把最可能影响并发的语句尽可能往后放,减小和其它事务冲突的概率。

# 3.2死锁

由于行锁只有当事务提交后才会释放,存在“持有锁资源的同时请求其它锁资源”的死锁问题,两个事务出现锁资源循环依赖,具体如下:

  • 事务A先拿到id=1的锁
  • 事务B拿到id=2的锁
  • 事务A要更改id=2记录,但是因为锁被事务B拿着,因此事务A持有锁的同时被阻塞。
  • 同理事务B要操作id=1,也会被阻塞。

# 3.3死锁解决方式

死锁监测一般有如下两种方式:

  • 方式1:等待事务超时,回滚。超时时间通过innodb_lock_wait_timeout设置。
  • 方式2:通过innodb_deadlock_detect设置为on开启死锁监测,发现死锁后主动回滚其中一条事务。

一般采用方式2解决死锁问题,但是在方式二中,每个死锁的线程都会检查所依赖的其它所有线程是否死锁,这是一个O(n*n)的复杂度。对于一条🔥热点数据而言,同时有1000个线程请求,则死锁监测操作量级在百万以上,从而消耗大量CPU资源。

# 3.4死锁监测机制带来的热点数据更新的性能问题解决方式

  • 关闭死锁监测

能够确保业务一定不会出现死锁,可以临时关闭死锁检测。否则当出现死锁的情况下还关闭监测,会导致大量业务超时。

  • 控制并发度

如果在客户端进行并发控制,即使并发度很小,但是如果部署的客户端应用很多,汇总到的服务端的线程数量也很多,因此这种方法不能完全解决问题。

✨药到病除的方法是在数据库进行并发控制,比如在服务端打入数据库之前,加个中间件记录当前数据库的并发线程数量。

✨另一种方法可以将一行的冲突打散成多行,比如对于共享资源,分表或者分行进行存储。最终统计还原时再将所有记录相加。

编辑 (opens new window)
#数据库
上次更新: 2023/12/15, 15:49:57
事务隔离
事务的隔离性和行锁

← 事务隔离 事务的隔离性和行锁→

Theme by Vdoing | Copyright © 2023-2024 blageCoder
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式