Mysql八股之锁

目录

一、全局锁

二、表级锁

2.1 表锁

2.2 元数据锁

2.3 意向锁

2.4 自增锁

三、行级锁

四、死锁


根据加锁的范围,Mysql中提供的锁大致可以分为全局锁、表级锁和行级锁三种。

-- 查看事务及锁信息
select from information_schema.innodb_locks;
select from information_schema.innodb_lock_waits;
select * from information_schema.innodb_trx;

一、全局锁

        全局锁很简单,其实就是对整个数据库实例加锁,此时整个数据库实例进入只读状态。Mysql提供了一个加全局锁的指令:

Flush tables with read lock

        当业务需要让整个库都处于只读状态时,就可以使用上述命令,之后其它线程的数据更新语句(增删改)、数据定义语句(建表、修改表结构)以及更新事务的提交都会被阻塞。

典型使用场景:全库逻辑备份,把整库每个表都select出来存成文本。

可能出现的问题:

  • 如果在主库上备份,那在此期间无法执行更新,业务会阻塞崩溃;
  • 如果在从库上备份,那在此期间从库不能执行主库同步过来的binlog,导致主从延迟,不过在可重复读级别下开启一个事务就能够拿到一致性视图。

        因此,全局锁一般不会使用,针对备份官方也提供了mysqldump工具,当mysqldump使用参数–single-transaction的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。而由于MVCC的支持,这个过程中数据是可以正常更新的。

二、表级锁

        Mysql中的表级锁分为三种:表锁;元数据锁(meta data lock,MDL);意向锁;自增锁。

2.1 表锁

        表锁可以通过lock tables ... read/write指令来实现,锁释放可以使用unlock tables ... 命令手动释放,也可以等待在客户端断开的时候自动释放。lock tables 语法除了会限制别的线程的读写外,也限定了本线程接下来的操作。

2.2 元数据锁

        元数据锁不需要显示开启,在访问表时会自动加锁。它的作用是保证读写的正确性,例如,当线程A执行查询遍历表A的数据,此时线程B对表A进行结构变更,那线程A查询得到的结果就会与表结构不一致。因此,在Mysql5.5引入了MDL,当对表进行增删查改时,自动加MDL读锁;当对表进行结构修改时,自动加MDL写锁。

需要注意的是:

  • 读锁之间不互斥,可以有多个线程同时对一张表进行增删查改;
  • 读写锁和写锁之间互斥,用于保证表结构变更的安全性,因此,如果有两个线程同时对一个表加字段,会串行执行;
  • 事务中的MDL锁,在语句执行开始时申请,但是语句结束后并不会马上释放,而会等到整个事务提交后再释放。

在线上环境时,表结构修改需要注意:

  • 如果是小表或者访问量不多的表,要避免长事务,因为如果事务不提交,会一直占着MDL锁,在MySQL的information_schema库的innodb_trx表中,可以查到当前执行的事务。如果要做DDL变更的表刚好有长事务在执行,要考虑先暂停DDL,或者kill掉这个长事务;
  • 如果是大表或者热点表,在alter table语句里面设定等待时间,如果在这个指定的等待时间里面能够拿到MDL写锁最好,拿不到也不要阻塞后面的业务语句,先放弃。之后再通过重试命令重复这个过程。

2.3 意向锁

        意向锁表示某个事务正在锁定一行或者将要锁定一行,表明一个意图,由存储引擎自己维护,是内部机制,用户无法操作意向锁。意向锁的出现是为了提高存储引擎的加锁效率,假设没有意向锁或者意向锁为行级锁,当事务A对表中某行数据加了排它锁且未提交,事务B此时也想对此表加锁,数据库需逐行判断表中是否存在被锁定的数据行,执行效率很低;但当意向锁作为表锁出现时,只需检查一次表中是否存在意向锁即可判断当前有无锁定的数据行,性能大为提升。

意向锁分为意向共享锁(IS)和意向排他锁(IX)。

  • IS:事务在给数据行加共享锁之前,会给数据行所在表也添加锁,这个表级锁就是意向共享锁。如果需要对记录 A 加共享锁,那么此时 InnoDB 会先找到这张表,对该表加意向共享锁之后,再对记录 A 添加共享锁。
  • IX:事务在给数据行加排他锁前,会对数据行所在表添加意向排他锁。当一个事务对表添加意向排他锁后,另外一个事务在加锁前就会通过该表的意向排他锁得知已经有事务在对该表进行独占操作,从而等待。

2.4 自增锁

        自增锁(AUTO-INC Locks)是一种特殊的表级锁,当一个事务正在向表中插入数据时,其它事务都将被阻塞,确保数据的连续自增。

三、行级锁

        行级锁是针对数据表中记录的锁,粒度最细发生冲突的几率也更低。在InnoDB事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放,这个就是两阶段锁协议。如果事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。

InnoDb引擎支持三类行级锁:

  • Record Lock:单行记录锁,读已提交隔离级别下默认使用Record Lock;
  • Gap Lock:间隙锁,锁定一个范围,但不包含记录本身,不允许在锁定期间往这个间隙中插入记录,能够解决数据库幻读问题;并且间隙锁之间不互斥;
  • Next-Key Lock(排它锁):Record Lock+Gap Lock,锁定一个范围,并锁定记录本身;在可重复读隔离级别下默认使用Next-Key Lock。

InnoDb存储引擎下Next-Key Lock加锁规则:

  • 加锁的基本单位是Next-Key Lock,Next-Key Lock是前开后闭区间;
  • 查找过程中访问到的对象才会被加锁;
  • 索引上的等值查询,当为唯一索引查询时,Next-Key锁退化为Record Lock;
  • 索引上的等值查询,向右遍历且最后一个值不满足等值条件的时候,Next-Key锁退化为间隙锁;
  • 唯一索引上的范围查询会访问到不满足条件的第一个值为止。

四、死锁

        在并发量较大的业务中,经常会出现数据库死锁问题,不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待的状态,例如:

事务A在等待事务B释放id=2的行锁,而事务B在等待事务A释放id=1的行锁。事务A和事务B在互相等待对方的资源释放,就是进入了死锁状态。当出现死锁以后,有两种策略:

  • 直接进入等待,直到超时。这个超时时间可以通过参数innodb_lock_wait_timeout来设置;在InnoDb引擎中,innodb_lock_wait_timeout默认值为50s;
  • 发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数innodb_deadlock_detect设置为on,表示开启这个逻辑,默认值即为on。

由于死锁超时等待时间太长,因此正常业务都会开启死锁检测。死锁检测在数据库发生死锁时,能够快速发现并进行处理,但是它有额外负担的,每当一个事务被锁的时候,就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待,也就是死锁;并且当所有事务都要更新同一行时,每个新来的被堵住的线程都要判断会不会由于自己的加入导致死锁,这是一个时间复杂度是O(n)的操作。

因此,为了解决上述问题,可以采用以下方法:

  • 评估该业务是否会出现死锁,不会则将死锁检测临时关闭;
  • 控制并发度;
  • 缩小锁粒度,减小冲突概率,从而减小死锁检测带来的CPU损耗。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值