Mysql 锁

InnoDB 存储引擎支持的锁

既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。

MyISAM存储引擎支持的锁

采用的是表级锁(table-level locking)

表级锁 VS 页级锁 VS 行级锁

类型开销加锁速度死锁粒度并发性能
表级锁开销小加锁速度快不会出现死锁粒度大发生冲突的概率大,并发度低
页级锁界于表锁和行锁之间界于表锁和行锁之间会出现死锁界于表锁和行锁之间并发度一般
行级锁开销大加锁速度慢会出现死锁粒度最小发生锁冲突的概率最低,并发度也最高。

乐观锁

什么是乐观锁

乐观锁总是假设不会发生冲突,因此读取资源的时候不加锁,只有在更新的时候判断在整个事务期间是否有其他事务更新这个数据。如果没有其他事务更新这个数据那么本次更新成功,如果有其他事务更新本条数据,那么更新失败。
乐观锁假设的场景是冲突少,因此适合读多写少的场景
无需像悲观锁那样维护锁资源,做加锁阻塞等操作,因此更加轻量化。

如何使用乐观锁

1、版本号

1 给每条数据都加上一个 version 字段,表示版本号
2 开启事务后,先读取数据,并保存数据里的版本号 version1,然后做其他处理
3 最后更新的时候比较 version1 和数据库里当前的版本号是否相同。用 SQL 语句表示就是 update t set version = version + 1 where version = version1。
update 操作时会进行当前读,因此即使是在可重复读的隔离级别下,也会取到到最新的版本号。如果没有其他事务更新过这条数据,那么 version 等于 version1,于是更新成功。如果有其他事务更新过这条数据,那么 version 字段的值会被增加,那么 version 不等于 version1,于是更新没有生效。

2、CAS算法

CAS 是 compare and swap 的缩写,翻译为中文就是先比较然后再交换。
CAS 操作中包含三个操作数 :
需要读写的内存位置V
进行比较的预期原值A
要写入的新值B

过程

如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B。
否则处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值

缺点
1、 ABA问题

一个线程T1从内存位置V中取出A,这时候另一个线程T2也从内存中取出A,并且T2进行了一些操作变成了B,然后T2又将V位置的数据变成A,这时候线程T1进行CAS操作发现内存中仍然是A,然后T1操作成功。尽管线程T1的CAS操作成功,但可能存在潜藏的问题

2、循环时间长开销大

自旋CAS(不成功,就一直循环执行,直到成功)如果长时间不成功,会给CPU带来非常大的执行开销

3、只能保证一个共享变量的原子操作

当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i = 2,j = a,合并一下ij = 2a,然后用CAS来操作ij。从Java 1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

悲观锁

悲观锁总是假设会发生冲突,因此在读取数据时候就将数据加上锁,这样保证同时只有一个线程能更改数据。表锁、行锁等都是悲观锁。悲观锁合适写多读少的场景

共享锁(读锁 S)

共享锁也成为读锁,读锁允许多个连接可以同一时刻并发的读取统一资源,互不干扰

排他锁(写锁 X)

排他锁也称为写锁,一个写锁会阻塞其他的写锁或读锁,保证同一时刻只有一个连接可以写入数据,同时防止其他用户对这个数据的读写。

如何显式指定共享锁或者排他锁

显式指定共享锁

SELECT ... from table_name LOCK IN SHARE MODE     (加共享锁)

显式指定排他锁

SELECT ... from table_anme FOR UPDATE             (加排他锁)

查看等待锁的情况

show engine innodb status;select * from information_schema.innodb_trx where trx_state="lock wait";

读锁和写锁的加锁关系

读锁和写锁的加锁关系如下

读锁写锁
读锁共存互斥
写锁互斥互斥

从这个表格里可以知道读锁和写锁不能共存,请考虑这样一个场景,一个请求占用了读锁,这时又来了一个请求要求加写锁,但是资源已经被读锁占据,写锁阻塞。这样本没有问题,但是如果后续不断的有请求占用读锁,读锁一直没有释放,造成写锁一直等待。这样写锁就被饿死了,为了避免这种情况发生,数据库做了优化,当有写锁阻塞时,后面的读锁也会阻塞,这样就避免了饿死现象的发生

意向锁

为了支持在不同粒度上进行加锁操作,InnoDB存储引擎支持一种额外的锁方式,称之为意向锁(Intention Lock), 即对最细粒度的对象进行上锁,那么首先需要对粗粒度的对象上锁。Mysql中Innodb存储引擎支持的意向锁比较简单,为表级别的锁,目的为了在一个事务中预示下一个将被请求的锁类型。由于支持的行级别的锁,所以意向锁不会阻塞除全表扫描以外的任何请求。

比如: 对页上的记录R进行上X锁,那么分别需要对数据库D、表、页上意向锁IX,最后对记录R上X锁。若其中任何一个部分导致等待,那么该操作需要等待粗粒度锁的完成。

意向共享锁(IS)意向排他锁(IX)

InnoDB的行级锁定同样分为两种类型,共享锁和排他锁,而在锁定机制的实现过程中为了让行级锁定和表级锁定共存,InnoDB也同样使用了意向锁(表级锁定)的概念,也就有了意向共享锁和意向排他锁这两种。
意向共享锁(IS Lock)事务想要获得一张表中某几行的共享锁
意向排他锁(IX Lock)事务想要获得一张表中某几行的排他锁

当一个事务需要给自己需要的某个资源加锁的时候,如果遇到一个共享锁正锁定着自己需要的资源的时候,自己可以再加一个共享锁,不过不能加排他锁。但是,如果遇到自己需要锁定的资源已经被一个排他锁占有之后,则只能等待该锁定释放资源之后自己才能获取锁定资源并添加自己的锁定。而意向锁的作用就是当一个事务在需要获取资源锁定的时候,如果遇到自己需要的资源已经被排他锁占用的时候,该事务可以需要锁定行的表上面添加一个合适的意向锁。如果自己需要一个共享锁,那么就在表上面添加一个意向共享锁。而如果自己需要的是某行(或者某些行)上面添加一个排他锁的话,则先在表上面添加一个意向排他锁。意向共享锁可以同时并存多个,但是意向排他锁同时只能有一个存在。所以,可以说InnoDB的锁定模式实际上可以分为四种:共享锁(S),排他锁(X),意向共享锁(IS)和意向排他锁(IX)

共享锁(S)排他锁(X)意向共享锁(IS)意向排他锁(IX)
共享锁(S)兼容冲突兼容冲突
排他锁(X)冲突冲突冲突冲突
意向共享锁(IS)兼容冲突兼容兼容
意向排他锁(IX)冲突冲突兼容兼容

如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁;

锁策略

锁的开销是较为昂贵的,锁策略其实就是保证了线程安全的同时获取最大的性能之间的平衡策略。

行锁 (line lock)

行锁可以最大限度的支持并发处理,当然也带来了最大开销,顾名思义,行锁的粒度实在每一条行数据。

表锁 (talbe lock)

表锁是mysql最基本的锁策略,也是开销最小的锁,它会锁定整个表;

[写操作]:若一个用户正在执行写操作,会获取排他的“写锁”,这可能会锁定整个表,阻塞其他用户的读、写操作;

[读操作]:若一个用户正在执行读操作,会先获取共享锁“读锁”,这个锁运行其他读锁并发的对这个表进行读取,互不干扰。只要没有写锁的进入,读锁可以是并发读取统一资源的。

通常发生在DDL语句\DML不走索引的语句中,比如这个DML update table set columnA=”A” where columnB=“B”.
如果columnB字段不存在索引(或者不是组合索引前缀),会锁住所有记录也就是锁表。如果语句的执行能够执行一个columnB字段的索引,那么会锁住满足where的行(行锁)。

Innodb行锁

实现方式

innodb行锁是通过给索引上的索引项加锁来实现的,如果没有索引,innodb将通过隐藏的聚簇索引来对记录加锁。innodb的行锁分为三种: Record lock、Gap lock、Next-Key Locks

Record lock

单条索引记录上加锁,record lock锁住的永远是索引,而非记录本身,即使该表上没有任何索引,那么innodb会在后台创建一个
隐藏的聚集主键索引,那么锁住的就是这个隐藏的聚集主键索引。所以说当一条sql没有走任何索引时,那么将会在每一条聚集索引后面加X锁

Gap锁

Gap 锁是 InnoDB 引擎为了避免幻读而引入的。InnoDB 引擎在可重复读隔离级别下可以避免幻读。间隙锁就是锁住数据行之间的间隙,避免新的数据插入进来。只有在进行当前读(当前读就是读这个数据最新的提交数据。)的时候才会加 gap 锁。
在索引记录之间的间隙中加锁,或者是在某一条索引记录之前或者之后加锁,并不包括该索引记录本身。
间隙锁是作用于索引记录之间或第一条索引记录之前或最后一条索引记录之后的锁。
间隙锁不会作用于使用唯一索引去检索唯一行记录的情况。
防止幻读。(当前读)

间隙锁到底是什么

如insert into t values(2,2),(6,6); 其实间隙锁并没有完全锁住k=2和k=6记录的插入,也就是锁住的不是具体的值,而是所有可能插入新值k=4的位置,在本例中也就是(2,2)至(6,6)之间的位置,因为索引是有序排列的,所以k=4只能插入(2,2)至(6,6)之间。

什么情况下产生间隙锁
  1. where条件后用非索引列约束的时候。
  2. where条件后用唯一索引、普通索引做范围查询时候( <、<=、>、>=、between and )
  3. where后用非唯一索引列约束(等值搜索、字符串的最左前缀 LIKE ‘张%’ )的时候
  4. 根据唯一索引delete、update 、select ··· lock in share mode 或 select ··· for update等值条件约束不存在的列的时候
  5. 对于多列联合唯一索引,如果查询没有包含所有列的等值查询也会产生间隙锁。例如,联合唯一索引column_1、cloumn_2、column_3的联合唯一索引,如果当前读语句是 ··· where column_1 = value_1 and cloumn_2 = value_2,则会产生间隙锁,因为没有包含column_3
Next-Key Locks

在默认情况下,mysql的事务隔离级别是可重复读,并且innodb_locks_unsafe_for_binlog参数为0,这时默认采用next-key locks,所谓Next-Key Locks,就是Record lock和gap lock的结合,即除了锁住记录本身,还要再锁住索引之间的间隙。
例如:某普通索引列当前值有:1, 10, 20,那么此时它的防插入锁区间分别是:
(-∞, 1], (1, 10], (10, 20], (20, +∞)

innodb通过范围条件加锁时使用next-key锁外,如果使用相等条件请求给一个不存在的记录加锁,innodb会使用next-key锁。

  • 对于主键索引: Next-Key Lock会自动降级为Record Lock,单单锁住一个行记录.这里主要是为了提高并发性,
  • 对于唯一辅助索引: 同主键索引一样.会自动降级为Record Lock锁
  • 对于普通的辅助索引: 会首先加上Next-KeyLock锁,并且在行记录后的空隙加上gap Lock锁.

索引对锁的影响

1.若查询的列包含唯一索引或主键,则行锁将被自动降级到记录锁
2.在不通过索引条件查询时,innodb会锁定表中的所有记录(升级为表锁)
3.行锁是针对索引加的锁,所以虽然是访问不同行的记录,如果使用相同的索引键,是会出现锁冲突的
4.当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,不论是使用主键索引,唯一索引或普通索引,innodb都会使用行锁来对数据加锁
5.即使在条件中使用了索引字段,但是否使用索引来检索数据是由mysql通过判断不同计划的代价来决定的,如果mysql认为全表扫描效率更高,它就不会使用索引,这种情况下innodb会对所有记录加锁。

InnoDB行锁优化

  1. 尽可能让所有的数据检索都通过索引来完成,从而避免InnoDB因为无法通过索引键加锁而升级为表级锁定
  2. 合理设计索引,让InnoDB在索引键上面加锁的时候尽可能准确,尽可能的缩小锁定范围,避免造成不必要的锁定而影响其他Query的执行;
  3. 尽量控制事务的大小,减少锁定的资源量和锁定时间长度;
  4. 尽可能减少基于范围的数据检索过滤条件,避免因为间隙锁带来的负面影响而锁定了不该锁定的记录;

InnoDB减少行锁死锁的方式

  1. 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生概率;
  2. 对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率。
  3. 相似业务模块中,尽可能按照相同的访问顺序来访问,防止产生死锁;

MVCC

英文全称为Multi-Version Concurrency Control,翻译为中文即 多版本并发控制。
乐观锁的一种实现方式
广泛应用于数据库技术,像Oracle,PostgreSQL等也引入了该技术,即适用范围广
MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读
Mysql数据库默认隔离级别:RR(Repeatable Read,可重复读),MVCC主要适用于Mysql的RC,RR隔离级别。MVCC并没有简单的使用数据库的行锁,而是使用了行级锁,row_level_lock,而非InnoDB中的innodb_row_lock.

MySQL InnoDB的当前读

像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁

MySQL InnoDB的快照读

像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本

实际上MVCC 就是为了实现读-写冲突不加锁,而这个读其实指的是快照读, 而非当前读, 当前读实际上是一种加锁的操作,是悲观锁的实现方式

MVCC的原理

MVCC多版本并发控制指的是 “维持一个数据的多个版本,使得读写操作没有冲突”
而在MySQL中,实现这么一个MVCC理想概念,就需要MySQL提供具体的功能去实现它,而快照读就是MySQL为我们实现MVCC理想模型的其中一个具体非阻塞读功能

MVCC的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主要是依赖记录中的 3个隐式字段,undo日志 ,Read View 来实现的
隐藏字段

每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段

DB_TRX_ID

6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID

DB_ROLL_PTR

7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)

DB_ROW_ID

6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引

DB_ROW_ID是数据库默认为该行记录生成的唯一隐式主键,DB_TRX_ID是当前操作该记录的事务ID,而DB_ROLL_PTR是一个回滚指针,用于配合undo日志,指向上一个旧版本

undo日志
insert undo log

代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃

update undo log

事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要,在快照读(select,当读的过程中有写的事务开始和提交,会造成读数据的脏读、不可重复读、幻读等)时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除。

Read View

Read View就是事务进行快照读(select * from)操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成事务系统当前的一个快照,记录并维护系统当前活跃事务(未提交事务)的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)。
Read View主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。

Readview中四个比较重要的参数

  1. m_ids:表示在生成ReadView时当前系统中活跃的读写事务的事务id列表。
  2. min_trx_id:表示在生成ReadView时当前系统中活跃的读写事务中最小的事务id,也就是m_ids中的最小值。
  3. max_trx_id:表示生成ReadView时系统中应该分配给下一个事务的id值。
  4. creator_trx_id:表示生成该ReadView的快照读操作产生的事务id。

注意max_trx_id并不是m_ids中的最大值,事务id是递增分配的。比方说现在有id为1, 2, 3这三个事务,之后id为3的事务提交了。那么一个新的读事务在生成ReadView时, m_ids就包括1和2, min_trx_id的值就是1,max_trx_id的值就是4。

这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见

  1. 如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自
    己修改过的记录,所以该版本可以被当前事务访问。
  2. 如果被访问版本的trx_id属性值小于ReadView中的min_trx_id值,表明生成该版本的事务在当前事
    务生成ReadView前已经提交,所以该版本可以被当前事务访问。
  3. 如果被访问版本的trx_id属性值大于ReadView中的max_trx_id值,表明生成该版本的事务在当前事
    务生成ReadView后才开启,所以该版本不可以被当前事务访问。
  4. 如果被访问版本的trx_id属性值在ReadView的min_trx_id和max_trx_id之间,那就需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问

基于RR可重复读隔离级别,实现的基本原理

在select读数据的过程中,m_ids首次发现未提交的事务信息不会因在查找过程中其他事务id提交而把该事务id排除在外,直至查询到该事务链中最后提交的事务

基于读已提交的隔离级别

m_ids:保存事务系统中的活跃的事务id,基于m_ids中的id信息在事务链中直到查找到非活跃的事务id(已提交的事务,不管事务提交是否在read view生成后),此时就认为是该事务id查询的信息

RC隔离级别是在执行sql时生成read view,RR隔离级别是在事务开始生成read view

purge

从前面的分析可以看出,为了实现InnoDB的MVCC机制,更新或者删除操作都只是设置一下老记录的deleted_bit,并不真正将过时的记录删除。
为了节省磁盘空间,InnoDB有专门的purge线程来清理deleted_bit为true的记录。为了不影响MVCC的正常工作,purge线程自己也维护了一个read view(这个read view相当于系统中最老活跃事务的read view);如果某个记录的deleted_bit为true,并且DB_TRX_ID相对于purge线程的read view可见,那么这条记录一定是可以被安全清除的。

MVCC解决了什么问题

数据库并发场景有三种,分别为:

  • 读-读:不存在任何问题,也不需要并发控制
  • 读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
  • 写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失

多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。

  • 在并发读写数据库时,可以做到在读(select)操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
  • 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题

MVCC不满意只让数据库采用悲观锁这样性能不佳的形式去解决读-写冲突问题,而提出的解决方案,所以在数据库中,因为有了MVCC,所以我们可以形成两个组合:

    MVCC + 悲观锁

    MVCC解决读写冲突,悲观锁解决写写冲突

    MVCC + 乐观锁

    MVCC解决读写冲突,乐观锁解决写写冲突

    这种组合的方式就可以最大程度的提高数据库并发性能,并解决读写冲突,和写写冲突导致的问题

死锁

死锁,就是产生了循环等待链条,我等待你的资源,你却等待我的资源,我们都相互等待,谁也不释放自己占有的资源,导致无线等待下去。 即两个或者多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。

死锁产生的条件

  • 不同线程并发访问同一张表的多行数据,未按顺序访问导致死锁;
  • 不同线程并发访问多个表时,未按顺序访问导致死锁;
  • RR 隔离级别下,由于间隙锁导致死锁。

如何避免死锁

  1. 更新Sql的where条件尽量用索引
  2. 减少范围更新,尤其非主键、非唯一索引上的范围更新
  3. 加锁顺序一致,尽可能一次性锁定所有需要行
  4. 尽量基于Primary 或者 Unique Key 更新数据
  5. 并发存取多个表时,应尽量约定以相同的顺序访问表或者行,比如两个更新数据的事务,事务A 更新数据的顺序 为1,2;事务B更新数据的顺序为2,1。这样更可能会造成死锁
  6. 在REPEATABLE-READ隔离级别下,如果两个线程同时对相同条件记录用SELECT…FOR UPDATE加排他锁,在没有符合该条件记录情况下,两个线程都会加锁成功。程序发现记录尚不存在,就试图插入一条新记录,如果两个线程都这么做,就会出现死锁。这种情况下,将隔离级别改成READ COMMITTED,就可避免问题。
  7. 大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小。
  8. 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率。
  9. 降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为gap锁造成的死锁。
    10.为表添加合理的索引。可以看到如果不走索引将会为表的每一行记录添加上锁,死锁的概率大大增大。

如何分析死锁

InnoDB 中,可以使用 SHOW INNODB STATUS 命令来查看最后一个死锁的信息。

show engine innodb status

其他锁

全局锁

全局锁会锁住整个数据库,MySQL 使用 flush tables with read lock 命令来加全局锁,使用 unlock tables 解锁。线程退出后锁也会自动释放。当加上全局锁以后,除了当前线程以外,其他线程的更新操作都会被阻塞,包括增删改数据表中的数据、建表、修改表结构等。全局锁的典型使用场景是全库的逻辑备份。

MDL锁

MySQL5.5版本引入了MDL锁(metadata lock),用于解决或者保证DDL操作与DML操作之间的一致性
MDL 锁也是一种表级锁,MDL 锁不需要显示使用。MDL 锁是用来避免数据操作与表结构变更的冲突,试想当你执行一条查询语句时,这个时候另一个线程在删除表中的一个字段,那么两者就发生冲突了,因此 MySQL 在5.5版本以后加上了 MDL 锁。当对一个表做增删查改时会加 MDL 读锁,当对一个表做结构变更时会加 MDL 写锁。读锁相互兼容,读锁与写锁不能兼容。
MDL 需要注意的就是避免 MDL 写锁阻塞 MDL 读锁。

事务 A 执行 select 后给表 t 加 MDL 读锁。事务 B 执行 select 后给表再次加上 MDL 读锁,读锁和读锁可以兼容。事务 C 执行 alter 命令时会阻塞,需要对表 t 加 MDL 写锁。事务 C 被阻塞问题并不大,但是会导致后面所有的事务都被阻塞,比如事务 D。这是为了避免写锁饿死的情况发生,MySQL 对加锁所做的优化,当有写锁在等待的时候,新的读锁都需要等待。如果事务 C 长时间拿不到锁,或者事务 C 执行的时间很长都会导致数据库的操作被阻塞。

为了避免这种事情发生有以下几点优化思路:
1 避免长事务。事务 A 和事务 B 如果是长事务就可能导致事务 C 阻塞在 MDL 写锁的时间比较长。
2 对于大表,修改表结构的语句可以拆分成多个小的事务,这样每次修改表结构时占用 MDL 写锁的时间会缩短。
3 给 alter 命令加等待超时时间

参考链接

https://blog.csdn.net/netease_im/article/details/104679075
https://blog.csdn.net/qq_34107571/article/details/78001309
https://mp.weixin.qq.com/s/ZPS2mMA0Wv_gCr1QesYYeQ
https://juejin.cn/post/6844903943135297550
https://blog.csdn.net/jh993627471/article/details/79023306
https://blog.csdn.net/luzhensmart/article/details/81675527
https://blog.51cto.com/12182612/2486731

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值