Mysql:全局锁和表锁


根据加锁的范围,MySQL 里面的锁大致可以分成全局锁、表级锁和行锁三类

全局锁

全局锁就是对整个数据库实例加锁

Flush tables with read lock (FTWRL)
unlock tables // 解锁

加锁之后整个库处于只读状态,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。

全局锁的典型使用场景是,做全库逻辑备份。也就是把整库每个表都 select 出来存成文本。

但是就算是备份,使用这种锁整个实例的方式代价也很大。有没有更好的方式既能让备份过程中更新不影响视图的逻辑一致,又能不对整个实例加锁呢?

有,就是在可重复读隔离级别下开启一个事务

官方自带的逻辑备份工具是 mysqldump。当 mysqldump 使用参数–single-transaction 的时候,导数据之前就会启动一个事务,来确保拿到一致性视图。而由于 MVCC 的支持,这个过程中数据是可以正常更新的

表级锁

MySQL 里面表级别的锁有两种:一种是表锁,一种是元数据锁(meta data lock,MDL)

表锁

表锁的语法:

lock tables … read/write;
unlock tables;  -- 解锁

既可以主动解锁也可以在客户端断开的时候自动释放锁。

read锁特性:

  • read锁是共享锁,可以被多个会话持有;其他会话不获取锁也能读取数据

  • 持有锁的会话只能读不能写,其他会话在锁被持有期间只能读,写会被被阻塞

  • 持有锁的会话不论是否正常终止,mysql都会隐式释放锁

write锁特性:

  • 持有表锁的唯一会话可以从表读取和写入数据

  • 在释放WRITE锁定之前,其他会话无法从表读取数据或将数据写入表

MDL

MDL 不需要显式使用,在访问一个表的时候会被自动加上。MDL 的作用是保证读写的正确性。

当对一个表做增删改查操作的时候,加 MDL 读锁;当要对表做结构变更操作的时候,加 MDL 写锁。

读锁之间不互斥,读写锁之间、写锁之间是互斥的。

对表进行DDL的时候,比如你增加字段、修改字段,或者加索引,需要考虑两个方面,一是这种操作会扫描全表,二是它们会获取MDL写锁,并且事务中的 MDL 锁,在语句执行开始时申请,但是语句结束后并不会马上释放,而会等到整个事务提交后再释放,可能会阻塞所有的增删改查操作。

在业务中安全的DDL一般要考虑两点:

  1. 解决长事务,事务不提交,就会一直占着 MDL 锁,如果DDL过程中发现长事务要么kill掉,要么等该事务运行完再执行

  2. 对DDL操作加入超时

行锁

MySQL 的行锁是在引擎层由各个引擎自己实现的。InnoDB 是支持行锁的, 而MyISAM不支持。

两阶段锁协议

在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议

这意味着,如果在同一个事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。

死锁和死锁检测

如果并发的不同线程都在彼此等待某临界资源,就可能导致死锁。这种时候通常有两个策略:

  1. 等待+超时,超时可以通过参数innodb_lock_wait_timeout 来设置,innodb该值的默认值是50s;

  2. 发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑。

由于超时时间不好把握,太长业务不能接受,太短容易误伤,所以通常使用策略二

死锁检测也是有额外负担的:每个新来的被堵住的线程,都要判断会不会由于自己的加入导致了死锁,这是一个时间复杂度是 O(n) 的操作(检测其他所有线程的所持有情况)。当出现热点行更新的时候,大量的死锁检测可能导致CPU利用率很高,但是却执行不了几个事务的情况。

这就引出了怎么解决由这种热点行更新导致的性能问题

  1. 如果确保业务不会出现死锁,可临时把死锁检测关掉,但是有风险;

  2. 控制并发度。两个方法,一是通过代码控制,从客户端或者mysql源代码端控制,对热点更新进行限流或者排队;二是一行改成逻辑上的多行来减少锁冲突。


文章作者: 木白
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 木白 !
评论
  目录