锁就是协调多个用户或者客户端并发访问某一资源的机制,保证数据并发访问时的一致性和有效性。

全局锁

MySQL 全局锁会关闭所有打开的表,并使用全局读锁锁定所有表。

FLUSH TABLES WITH READ LOCK;
UNLOCK TABLES;

当执行 FTWRL 后,所有的表都变成只读状态,数据更新或者字段更新将会被阻塞。

场景

一般用在整个库(包含非事务引擎表)做备份(mysqldump 或者 xtrabackup)时。

mysqldump 包含一个参数 --single-transaction,可以在一个事务中创建一致性快照,然后进行所有表的备份。因此增加这个参数的情况下,备份期间可以进行数据修改。但是需要所有表都是事务引擎表。所以建议使用InnoDB 存储引擎。

表级锁

表级锁有两种:表锁和元数据锁。

表锁

场景

  1. 事务需要更新某张大表的大部分或全部数据。如果使用默认的行锁,不仅事务执行效率低,而且可能造成其它事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高事务执行速度;
  2. 事务涉及多个表,比较复杂,可能会引起死锁,导致大量事务回滚,可以考虑表锁避免死锁。
lock tables t14 read;
lock tables t14 write;

表读锁本线程和其它线程可以读,本线程写会报错,其它线程写会等待。

元数据锁

MDL不需要显式使用,在访问一个表的时候会被自动加上。

MDL的作用是,保证读写的正确性。MDL 锁的出现解决了同一张表上事务和 DDL 并行执行时可能导致数据不一致的问题。

对开发而言尽量避免慢查询,事务要及时提交,避免大事务。

对于 DBA 来说,也应该尽量避免在业务高峰执行 DDL 操作。并且DDL期间需要避免长事务。

在alter table语句里面设定等待时间,如果在这个指定的等待时间里面能够拿到MDL写锁最好,拿不到也不要阻塞后面的业务语句,先放弃。

ALTER TABLE tbl_name NOWAIT add column ...
ALTER TABLE tbl_name WAIT N add column ...

行锁

  • InnoDB 支持事务:适合在并发条件下要求数据一致的场景。
  • InnoDB 支持行锁:有效降低由于删除或者更新导致的锁定。

两阶段锁

行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。

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

行锁

两种类型的行锁:

  • 共享锁(S):允许一个事务去读一行,阻止其它事务获得相同数据集的排他锁;
  • 排他锁(X):允许获得排他锁的事务更新数据,阻止其它事务取得相同数据集的共享读锁和排他写锁。

共享锁(S):select * from table_name where … lock in share mode;
排他锁(X):select * from table_name where … for update(当前读)

行锁算法

Record Lock:单个记录上的索引加锁。
Gap Lock:间隙锁,对索引项之间的间隙加锁,但不包括记录本身。
Next-Key Lock:Gap Lock + Record Lock,锁定一个范围,并且锁定记录本身。

加锁规则

5.x系列<=5.7.24,8.0系列 <=8.0.13

  1. 原则1:加锁的基本单位是next-key lock,next-key lock是前开后闭区间。
  2. 原则2:查找过程中访问到的对象才会加锁。
  3. 优化1:索引上的等值查询,给唯一索引加锁的时候,next-key lock退化为行锁。
  4. 优化2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁。
  5. 一个bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

加锁分析

非索引字段查询(RC)

如果一个条件无法通过索引快速过滤,那么存储引擎层面就会将所有记录加锁后返回,然后由 server 层进行过滤。

唯一索引查询(RC)

如果查询的条件是唯一索引,那么 SQL 需要在满足条件的唯一索引上加锁,并且会在对应的聚簇索引上加锁。

非唯一索引查询(RC)

如果查询的条件是非唯一索引,那么 SQL 需要在满足条件的非唯一索引上都加上锁,并且会在它们对应的聚簇索引上加锁。

非索引字段查询(RR)

RR 隔离级别下,非索引字段做条件的当前读不但会把每条记录都加上 X 锁,还会把每个 GAP 加上GAP 锁。(条件字段加索引的重要性!)

唯一索引查询(RR)

如果能确保索引字段唯一,那其实一个等值查询,最多就返回一条记录,而且相同索引记录的值,一定不会再新增,因此不会出现 GAP 锁。

以唯一索引为条件的当前读,不会有 GAP 锁。

非唯一索引查询(RR)

新增GAP锁+对应数据的X锁

悲观锁

借助数据库锁机制在修改数据之前锁定,再修改的方式被称为悲观并发控制。

优点:利用锁机制保证了数据的顺序执行,不需要自己控制,加锁、释放完全由数据库代劳
缺点:一旦一个事务获取了锁,其他的事务必须等待,势必会影响系统的吞吐量

适用场景:写入操作比较频繁的场景

乐观锁

假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据冲突与否进行检测。

优点:由于不需要加锁,其他的事务可以同时操作数据,相比于悲观锁,系统吞吐量会提高
缺点:锁机制,如果并发度较高,失败重试的情况会成为系统瓶颈

适用场景:读取操作比较频繁的场景

锁定位

1、show processlist,查看state

死锁

死锁是指两个或者多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。

“唯一键”引起的死锁

会话 A获取了排它锁开始插入,之后的事务(“会话 B”,“会话 C”)再去执行时会出现 Duplicate Key(重复的值)问题,此时它们都会去申请该行记录的共享锁。如果这个时候,占据排它锁的事务出现回滚(“会话 A”),另外的两个事务会同时去申请排它锁。但是,在数据库中,排它锁和共享锁是互斥资源,也就导致了死锁。

之所以在出现 Duplicate Key 时会加上共享锁,是因为冲突检测是读操作,所以,冲突之后的轮询仍然会有共享限制。

解决方法

  1. 检测到死锁的循环依赖,立即返回一个错误,将参数 innodb_deadlock_detect设置为 on 表示开启这个逻辑;
  2. 等查询的时间达到锁等待超时的设定后放弃锁请求。这个超时时间由 innodb_lock_wait_timeout 来控制。默认是50 秒。

方案1有额外的CPU检测开销,确保无死锁时建议关闭检测。或者将一行改成逻辑上的多行来是控制并发度,减少锁冲突。以影院账户为例,可以考虑放在多条记录上,比如10个记录,影院的账户总额等于这10个记录的值的总和。

降低死锁概率

  1. 更新 SQL 的 where 条件尽量用索引;
  2. 基于 primary 或 unique key 更新数据;
  3. 减少范围更新,尤其非主键、非唯一索引上的范围更新;
  4. 加锁顺序一致,尽可能一次性锁定所有需要行;
  5. 将 RR 隔离级别调整为 RC 隔离级别。

分析死锁

SHOW FULL PROCESSLIST; //State字段,waiting for ... lock
show engine innodb status\G; //查看最后一次死锁信息

另外设置 innodb_print_all_deadlocks = on 可以在 err log 中记录全部死锁信息。

INNODB_TRX 表记录了当前处于运行状态的所有事务,包含非常详细的信息,例如:事务是否正在等待一个锁、事务是否正在执行等等。

INNODB_LOCKS 记录的是 InnoDB 事务去请求但没有获取到的锁信息和事务阻塞其他事务的锁信息。

INNODB_LOCK_WAITS记录了事务的锁等待状态。


   转载规则


《锁》 wangyixin-tom 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
事务 事务
事务ACID、隔离级别及实现。分析了事务可见性,介绍了Binlog、redolog,并加以对比。数据库不丢数据的设置,给出了使用事务的建议。
2021-03-14
下一篇 
索引 索引
数据结构B 树B 树每个节点都包含 key 值和 data 值。 如果 data 比较大时,每一页存储的 key 会比较少; 当数据比较多时,要经历多层节点才能查询在叶子节点的数据。 B+ 树 所有叶子节点中包含了全部关键字的信息 各叶子节
2021-03-13
  目录