MySQL锁

全局锁:

  • 通过flush talbe with read lock语句可以将整个数据库变成只读状态,这时候线程执行增删改或者表结构被修改都会阻塞,适用于全库备份逻辑。这样在备份数据库期间,不会因为表结构和数据的更新,使得备份文件的数据和预期不一样

表级锁:

  • 表锁: 通过lock tables语句对表加表锁,表锁除了会限制其他线程的读写,还会限制本线程的读写
  • 元数据锁:当我们对数据库表做操作,会自动给这个表加上MDL,对一张表做CRUD操作时,加的是MDL读锁;对一张表做结构更改时,加的是MDL写锁。这样对一张表执行CRUD操作时,防止其他线程对表做结构变更
  • 意向锁:当执行插入、更新、删除操作时,需要先对表加上意向独占锁,然后对该记录加上独占锁。意向锁的目的是快速判断表里是否有记录被加锁

行级锁(只有InnoDB引擎有行级锁):

  • 记录锁(Recode Lock):锁住的是一条记录。而且记录锁有S锁和X锁之分,满足读写互斥、写写互斥
  • 间隙锁(Gap Lock):只存在于可重复读隔离级别,此外间隙锁是兼容的,不会出现互斥关系,目的是解决可重复读隔离级别下幻读的现象
  • 临键锁(next-key Lock):是记录锁+间隙锁的组合, 锁定一个范围,并且锁定记录本身
  • 插入意向锁:当插入位置的下一条记录上有间隙锁,就会生成插入意向锁,然后进入阻塞状态。但是它不是意向锁,它是一种特殊的间隙锁,因为只锁住一个点

MySQL怎么实现乐观锁

可以在数据库表增加一个版本号字段,利用这个版本号字段实现乐观锁

具体的实现是每次更新数据的时候,都要带上版本号,同时将版本号+1。比如现在要更新id = 1,版本号为2的记录。这时候要先获取版本号为1的记录,只有版本号一致的情况下才能做出修改,如果不相等就不更新,需要重新获取该记录的最新版本号再次尝试

在线上修改表结构,会发生什么

线上环境可能有很多事务都在读写这张表,会对这张表加上MDL读锁。修改表结构会对表加上MDL写锁,会发生读写冲突,所以修改表结构的操作就会阻塞,并对后续事务的增删改操作都会阻塞

创建索引的时候会锁表吗

会的,创建索引会加MDL写锁,如果这时候其他事务对这张表进行增删查改的话,会产生MDL写锁,因为读写冲突,就会阻塞

间隙锁的工作原理是什么

间隙锁防止其他事务往间隙插入新记录,从而避免幻读。具体的原理是其他事务插入一条记录时,发现插入位置的下一条记录有间隙锁,就会生成插入意向锁,然后锁设置为阻塞状态,目的是告诉用户插入的位置存在间隙锁

一条update语句不带where,加的是什么锁

  • 可重复读级别下,没有带where,会进行全盘扫描,每一条记录加上next-key锁
  • 读已提交级别下,没有带where,也会全盘扫描,每条记录加上记录锁

带了where条件没有命中索引,加的是什么锁

  • 可重复读级别下,会进行全盘扫描,每一条记录加上next-key锁
  • 读已提交级别下,也会全盘扫描,每条记录加上记录锁

两条更新语句更新同一条记录的不同字段,加的是什么锁

可重复读隔离级别下,可能有这些情况:

  1. 如果更新的字段是唯一索引,还要看更新的记录是否存在:

    • 如果存在,那么这条记录加的是记录锁,只会锁住该记录
    • 如果不存在,会加间隙锁
  2. 如果更新的字段是非唯一索引,也要看更新的记录是否存在:

    • 如果存在,由于非唯一索引会有相同的值,所以实际上非唯一索引等值查询是一个扫描的过程,会对扫描到的符合条件的记录都加上next-key锁,最后扫描到第一个不符合的条件记录停止扫描,并且加上间隙锁。同时,在符合条件的主键索引上加上记录锁
    • 如果不存在,会对第一个不符合更新条件的二级索引记录加上间隙锁

可重复读隔离场景下,下面的场景会发生什么

img

事务A和事务B在执行完更新语句后,由于记录不存在并且间隙锁兼容,都会加上(20,30)的间隙锁,而接下来的插入操作为了获取到插入意向锁,都会等待对方的间隙锁释放,就会造成死锁

了解过MySQL死锁问题吗

在并发事务中,两个事务出现了循环资源依赖,这两个事务都在等待另一方释放资源,从而进入了无限等待的状态,就有了死锁

比如一个例子:

  • 一张表有一个id为20和id为30的记录,事务A和事务B在执行完更新id = 25的语句后,由于记录不存在并且间隙锁兼容,都会加上(20,30)的间隙锁,而接下来的插入操作为了获取到插入意向锁,都会等待对方的间隙锁释放,就会造成死锁

MySQL怎么避免死锁

通过show engine innodb status命令获取死锁信息

日志上半部分说明了事务1在等待什么锁,下半部分说明了事务持有的锁和等待的锁

因此通过阅读日志就可以找到死锁发生的原因,可以进一步处理

MySQL怎么避免死锁

实际上死锁是无法避免的,但我们可以通过一些手段,降低死锁发生的概率

  1. MySQL的锁是在事务提交后才被释放,所以我们可以缩短锁持有的时间,降低死锁的概率,比如:

    • 如果事务中需要锁定多个行,要把最可能发生锁冲突的申请时机往后放,这样事务锁的持续时间就比较短
    • 避免大事务,尽量讲大事务拆分成几个小事务,这个锁持有的时间就比较短
  2. 可以通过减少间隙锁,降低死锁的概率:

    • 如果能确定幻读和不可重复读对应用的影响不大,可以将隔离级别改成读提交,这样就没有了间隙锁,降低了死锁的概率
  3. 可以通过减少锁的范围,降低死锁的概率:

    • 给表添加合理的索引,如果不走索引表的每一行记录都会加上行级锁,发生死锁的概率大
  4. 通过设置MySQL参数,降低死锁的概率:

    • 设置锁等待超时阈值”innodb_lock_wait_timeout”,当一个事务的等待时间超过该值后,事务将被回滚,使用“innodb_rollback_on_timeout”开启值为ON,开启这个参数后,锁超时后就会回滚
    • 开启主动死锁检测”innodb_deadlock_detect”,主动死锁检测在发生死锁后,主动回滚死锁链条中的某一个事务,让其他事务继续执行