1.MYSQL 锁。《MYSQL核心》系列开更!
“ 对不起,我不演了,摊牌了其实我被绑架了,绑匪要我替他写100篇文章。否则就把我送去非洲当酋长~ ”
前菜结束,来点硬菜!
本文大纲:
系列简介及期许
锁的介绍。
全文共2725个字,请合理安排时间。
01
—
系列简介及期许
开始更新mysql核心系列,知识面非常广泛,抽象。
背景:
数据库是世界上最复杂的系统之一。本文会尽可能的介绍其原理,但不会过于深究其底层的算法实现。
作者的废话:
其实初稿说了很多衍生的概念和举例,最后都删掉了,考虑后明确了定位,说得再基础也得谈核心,考虑所有受众便显得累赘,需要门槛只说重点,权当作为开发面试和自我提升的小文章。
本系列的期许:
不是入门教程(入门网上多的是,不做搬运工),期望是自我总结和尽可能用简单的语言来阐述复杂的原理。(本系列文章会根据反馈不断改进)
书籍建议:《高性能mysql》《官方英文文档》
知识储备及前提:
1.本文讨论的锁以基于innodb引擎为例,不说隔离级别以MySQL默认RR,锁的内容非常复杂,用一篇文章的篇幅是很难完全概括的,因此本文介绍的是绝大多数场景碰到的锁的核心知识。
2.阅读该文章前需要了解的基础知识:
1.事务及事务隔离级别(下一期文章细谈)锁的主要应用就是隔离级别,所以说起锁就得带着隔离级别谈,这两者密切度非常高。本文中会带很多隔离级别相关的内容,看不懂的请自行查阅资料或者等下一期文章一起看。
2.基本的索引相关认识,如聚簇索引。
3.MVCC多版本并发控制。(后续文章细谈)
02
—
锁的介绍
什么是锁及为什么要设计锁?
简单来说锁是数据库对同一资源访问的冲突解决策略。
根据锁的类型分为读锁(共享锁),写锁(排他锁)。(读锁之间不互斥,读写锁之间互斥)
根据锁的范围(颗粒度)又可分为行级锁,表级锁,全局锁,间隙锁,Next-Key Lock(临键锁)。
全局锁:
有两种加锁方式
1.一般用于全库逻辑备份(当然现在已经支持热备份),命令为FLUSH TABLE WITH READ ONLY,该命令执行后,全库将变为只读状态,不可被编辑。DML(数据操作语言)与DDL(数据定义语言)语句皆无法执行。
2.命令为set global readonly = true,与前者的区别主要在客户端异常断开时,前者会释放锁资源,后者会一直保持readonly风险很高。也很容易理解毕竟后者是全局配置项,不手动改回来是不会解锁的。还有一点,配置可能会被用于其他逻辑例如判断读写分离的主从库。
表级锁:
一般有2种表锁和元数据锁(MDL),分别针对于表数据与表结构。
表锁语句为:加锁lock table ... read/write 解锁unlock table 或者自动断开解锁。需要注意的是,加锁后不单会对其他线程造成影响,当前线程下也会立即生效。
MDL锁为5.5后引入的概念,会在一些场景下自动加上例如当有增删改查的时候会将该表增加MDL读锁,当对表结构进行变更时加MDL写锁。理由很简单,在增删改查时如果同时对表结构发生变更,将会引起数据一致性问题,当对表结构进行变更时,如果去增删改查也是同理。
注意:MDL写锁如果发生阻塞,很有可能会会导致整个库崩溃。比如有2个事务,事务A申请了MDL读锁,这个时候事务B需要变更表结构,因此会申请MDL写锁,但此时事务A并未结束释放,因此事务B会被阻塞,此时更可怕的情况是,在事务B未完成之前,该库的MDL读锁也无法被申请,因此该库将会不可被读写。后续的请求将会把库撑爆。
避免的方法有2种,1.是避免长事务(这个在任何情况下都建议)2.在业务不繁忙的时段更改表结构,在申请MDL写锁时需要判断在指定时间范围内是否可以拿到,如果不行就放弃。
注意:锁资源是在事务提交后释放的。切记,补充概念两阶段锁(指在需要加锁时会立马加上,但在释放时需要等待事务结束)
行锁:
注意(myisam最小只有表锁无行锁,同一张表上只有一个更新在执行(因为写锁),但是可以多个查询(因为只有读锁),因此并发能力弱)
顾名思义,行锁即在需要访问的行加上锁,锁的颗粒度小于表锁,因此可以在同一时间支持多个更新语句(非串行化隔离级别),最大程度支持了并发度,但同时也带来了最大的锁开销。
间隙锁(Gap Lock)
是Innodb在当前读(可重复读隔离级别下)情况下为了解决幻读问题时引入的锁机制。间隙锁是一种共享锁(某些文章中说是排他锁,但目前总结出来是共享锁。排他锁会导致另一个事务不可加读锁的数据。),多个事务可以对同一个间隙添加间隙锁。
它是一种区间锁,用于锁住将来可能插入或者删除的数据行。
举例:
id(自增唯一索引) |
name |
pid(非唯一索引) |
2 |
小 |
5 |
3 |
王 |
6 |
4 |
八 |
7 |
加锁情况:
非唯一索引等值当前读查询 select * from table where pid = 6 for update ,由于索引的B+树有序结构,会在6上加写锁,(5,6),(6,7)增加间隙锁。
非唯一索引范围当前读查询 select * from table where pid > 6 for update ,(5,6),(6,7)加间隙锁。
自增唯一索引范围当前读查询 select * from table where id > 3 for update ,会在(3,正无穷)加间隙锁
自增唯一索引等值当前读查询 select * from table where id = 3 for update 不加间隙锁
next-key lock(临键锁)
其实是属于行锁的一种,它是行锁与间隙锁的结合,只在RR 上出现。
行锁防止别的事务修改或删除,GAP锁防止别的事务新增,行锁和GAP锁结合形成的的Next-Key锁共同解决了RR级别在写数据时的幻读问题。
MVCC解决的是快照读的幻读问题,临键锁解决的是当前读下的幻读问题。
关于修改丢失,脏读,不可重复读,幻读等现象与解决方案将在下一篇文章《事务及其隔离级别》中介绍
锁的应用,关于四种隔离级别下的加锁行为,非常重要,请结合场景理解
1. 未提交读(READ_UNCOMMITED)解决丢失修改
规定:
1.事务A对当前被读取的数据不加锁。
2.事务A开始更新一行数据时,必须先对其加共享锁,直到事务结束才释放。
从第二点就可以看出,事务A在写入数据的时候加了共享锁,其他事务只能读,不能写,所以事务A在写的过程中也就不会被覆盖,从而就解决了丢失修改的问题。
2. 提交读(READ_COMMITED)解决脏读
规定:
1.事务A对当前被读取的数据加共享锁,一旦读完该行,立即释放该共享锁。
2.事务A在更新某行数据时,必须对其加上排他锁,直到事务结束才释放。
(可见两阶段锁协议是不是一定的,而要区分隔离级别场景,还有别的特殊场景,例如数据库server对非索引查询时的优化)
3. 可重复读(REPEATABLE READ)解决不可重复读
规定:
1.事务A在读取某数据时,必须先对其加共享锁,直到事务结束才释放。
2.事务A在更新某数据时,必须先对其加排他锁,直到事务结束才释放。
4. 串行化(SERIALIZABLE)解决幻读
最容易理解的锁行为,对记录读加表读锁,写加表写锁 。
规定:
1.事务在读取数据时,必须先对其加表级共享锁 ,直到事务结束才释放。
2.事务在更新数据时,必须先对其加表级排他锁 ,直到事务结束才释放。
一周到2周更新一次,与君共勉。
写作经历不多,写得不对的地方还请及时提出。转载请标明出处~