![7534a65e53842c013b595eb2abcbc757.png](https://i-blog.csdnimg.cn/blog_migrate/758442bda7e5885b408cd1b6f78d9075.jpeg)
一、MySQL中的锁与锁策略
在MySQL中,为了应对并发场景下的读写,锁通常分为两类:共享锁以及排他锁。其中,共享锁允许多个连接在同一时间并发的读取相同的资源,彼此之间互不影响,所以又称为读锁。排他锁则会阻塞其他尝试获取共享锁或者排他锁的操作,确保同一时间只有一个连接可以写入数据,并禁止其他用户的读写,又称写锁。
关注“网易云信”公众号,回复“知乎”,获取2019技术干货合集>>>
免费试用网易云信音视频产品>>>>
在实际使用下,加锁往往意味着高昂的开销,MySQL为了平衡锁的开销以及并发的线程之间的安全,采用了两种不同的锁策略:
- table lock(表锁)
表锁会锁定整张表,如果当前有用户正在执行写操作并且获取了写锁,这可能导致整张表被锁定,阻塞其他用户的读写操作。如果用户执行的是读操作,则会获取读锁,此时其他用户的并发读操作将被接受,写操作会被阻塞。
举个例子,执行语句:
![56b4d07d6668144c13b8c0b6efa4e5ca.png](https://i-blog.csdnimg.cn/blog_migrate/29b0c94bd9a8ef230ed89e05be3ea54d.png)
如果b字段不存在索引,那么会锁住所有的记录,即锁上了表锁。
- row lock(行锁)
行锁的粒度是在每一条行数据,这意味行锁可以尽可能的支持并发处理,相应的行锁开销也会比较大。并且,在InnoDB中的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则行锁将会自动升级为表锁。
相比较而言,表锁的优势在于开销小,加锁快,无死锁,劣势是锁的粒度大,发生锁冲突的概率较高,并发能力较弱。而行锁则相反。实际使用中,两者都会由MySQL自动加锁。行锁冲突可以通过执行 show status like 'innodb_row_lock%'语句进行分析,表锁冲突则可通过执行show status like 'table_locks%' 进行查看。
二、MySQL中的事务与隔离级别
事务就是一组原子性的sql,要么MySQL引擎会全部执行这一组sql语句,要么全部不行(不允许任何一条失败)。失败的语句将导致事务的整个回滚。事务系统通常满足四个特性,分别为原子性(要么全部执行、要么全部回滚)、一致性(数据必须从一个一致性状态转换为另一种一致性状态)、隔离性(事务未执行成功,其他人无法看到结果)、持久性(事务在commit之后,数据不会丢失)。
由上述概念可知,事务是用来保障数据的一致性以及完整性的。也是MySQL中用来平衡效率与安全之间的一种手段,所以,InnoDB引擎下的事务通常提供了四种事务的隔离级别,方便用户自己在效率和安全之间做出权衡。
- READ UNCOMMITED(未提交读)
事务中的修改,即使该事务未提交,对其他的事务也是可见的。可以读取到其他事务中的数据,又称为脏读,在实际数据库事务中,脏读会破坏数据的一致性,对业务产生极大影响,所以一般不推荐采用READ UNCOMMITED作为数据库事务的隔离级别。
![7c57ca6e47ced2078fbe4bfc2737a8cd.png](https://i-blog.csdnimg.cn/blog_migrate/76ea3429f83478588e44ec3c5e547bed.png)
- READ COMMITED(提交读)
在提交读级别中,数据库将保证如果一个事务没有完全执行成功(commit完成),事务中的操作对其他的事务是不可见的。在该隔离级别下,虽然杜绝了脏读的发生,但是还是存在着不可重复读以及幻读的问题。不可重复读发生在事务T1读取了一行数据,事务T2接着修改或者删除了该行数据(已提交),当T1事务再次读取同一行数据的时候,发现数据已经被修改或者被删除。示例如下图:
![3c259fb58f616fce2e23655510358027.png](https://i-blog.csdnimg.cn/blog_migrate/10f5ddfe81329ed4e5015c46fd60dac9.png)
幻读则发生在事务T1读取了满足某条件的一个数据集,事务T2此时插入了一行或者多行满足T1查询条件的的数据并提交,当T1再次采用相同的条件进行读取时,得到了与第一次不同的结果集。示例如下:
![9f17501bc0c3e954220f9ec3083d309f.png](https://i-blog.csdnimg.cn/blog_migrate/4055c37e975601ef3be5a2de10634b11.png)
- REPEATABLE READ(提交读)
REPEATEABLE READ是MySQL的默认隔离级别,它确保同一个事务的多个实例在并发读取数据时,会看到同样的数据。按照该隔离级别定义,还是会存在幻读的问题。MySQL通过InnoDB存储引擎的多版本并发控制机制(MVCC)解决了部分该问题的场景,但仍然存在,此处后文会另有分析。
- SERIALIZABLE(串行化)
串行化是最严格的隔离级别,通过给事务中的每次读写操作都加锁,保证了不产生任何
脏读、不可重复读以及幻读问题,但是随之引入的是大量的读超时以及锁竞争,导致数据库性能的严重下降。
另外来看看ANSI SQL STANDARD中,对于数据库隔离级别以及相应问题的规定:
![84b2ae1ff7698680022852411e1d0174.png](https://i-blog.csdnimg.cn/blog_migrate/0372e1c4c66495da8478b902694acede.jpeg)
所以,对于REPEATABLE READ隔离级别下,是允许出现幻读的。
三、MySQL中的MVCC
MVCC(multiple-version-concurrency-control)是个行级锁的变种,它在普通读情况下避免了加锁操作,因此开销更低。其原理具体为,在InnoDB存储引擎中,每行数据会加入一些隐藏字段DATA_TRX_ID,DATA_ROLL_PTR,DB_ROW_ID,DELETE_BIT。DATA_TRX_ID 字段记录了数据的创建和删除时间,这个时间指的是对数据进行操作的事务的id,DATA_ROLL_PTR 指向当前数据的undo log记录,回滚数据就是通过这个指针,DELETE BIT位用于标识该记录是否被删除,这里的不是真正的删除数据,而是标志出来的删除。真正意义的删除是在mysql进行数据的GC,清理历史版本数据的时候。
相应的,其DML的处理方式也发生了变化:
SELECT语句先查找DATA_TRX_ID早于当前事务ID的数据行。这样就保证了读取的数据要么是在这个事务开始之前就已经commit了的(早于当前事务ID),要么是在这个事务中自身创建的数据(等于当前事务ID)。查找行的DELETE_BIT为1时,查找删除事务ID对应的事务,确定此条记录在当前事务开始之前,行没有被删除。
INSERT语句会在新插入行数据之后,保存当前事务ID作为行的DATA_TRX_ID。
DELETE语句为每一条删除的记录保存当前的事务ID作为行的删除标记。
UPDATE语句将复制变更的记录,并把新记录的DATA_TRX_ID置为当前事务ID,同时更新老记录中的DB_ROLL_PT指向了上一个版本。
所以在并发读的时候,不需要等到访问行上的锁释放,只需要读取一个行的快照即可。既然是多版本的读取,就肯定读取不到其他事务中的新插入的数据了,也就避免了上述场景中提到的幻读。
四、幻读
从上述信息我们已经知道,在REPEATABLE READ级别下,InnoDB采取多版本策略成功
避免了部分幻读现象,但是实际使用中,还是会有幻读产生,先看场景:
![be8f794a6ea108a140886e1e32563f04.png](https://i-blog.csdnimg.cn/blog_migrate/7383378874a10fd9e3f1ef6876fc53df.jpeg)
通过MVCC,在事务中的多次读取不会出现幻读,但是此时的插入操作依旧会发生主键重复的错误,并且因为MVCC机制,在上图中的会话1无论读取多少次都不会读到导致冲突产生的数据,确实就如“幻影”一般诡异。
为了解决上述场景中的幻读,需要简单提一下InnoDB的行锁机制,在InnoDB引擎下存在三种行锁,分别为:
-
- Record Lock:在单行记录上的锁
- Gap Lock:间隙锁,锁定一个范围,但不包括记录本身,。GAP锁的目的,是为了防止同一事务的两次读出现幻读的情况
- Next-Key Lock: 前两个锁的共同使用,即锁定了记录本身,也锁定了一定的范围。
通常情况下,INSERT/UPDATE/DELETE默认会在操作的记录上加上Next-Key Lock,而
普通的SELECT因为MVCC的关系反而只需要读取快照即可,所以如果业务需要再REPEATABLE READ场景下保证绝对不产生幻读,需要手动给SELECT加锁,在类似SELECT…WHERE加入FOR UPDATE(排它锁)或者LOCK IN SHARE MODE(共享锁)
五、总结
- InnoDB中使用索引作为检索条件修改数据时采用行锁,否则使用表锁
- InnoDB自动给修改操作加锁,给查询操作不自动加锁
- 在REPEATABLE READ级别下,如果要完全杜绝幻读,需要手动给关键查询语句加锁
- 表的大部分数据需要修改时,行锁反而不如表锁更有效率
最后,本文只是就网易云信业务中数据库的一些使用场景和问题作了总结,数据库的实
际使用还存在诸多学问,希望能抛砖引玉,让更多人分享出自己的心得。
关注“网易云信”公众号,回复“知乎”,获取2019技术干货合集>>>
免费试用网易云信音视频产品>>>>
![71021650c1e1d0aaebf37633c05eb8ac.png](https://i-blog.csdnimg.cn/blog_migrate/a16d4d906ecce308ed19658f5d6f5de0.png)
邀请好友使用网易云信,好友下单成功即可获得1000元网易考拉/严选无门槛现金券,点击立即推荐>>
了解网易云信,来自网易核心架构的通信与视频云服务>>
网易云信(NeteaseYunXin)是集网易19年IM以及音视频技术打造的PaaS服务产品,来自网易核心技术架构的通信与视频云服务,稳定易用且功能全面,致力于提供全球领先的技术能力和场景化解决方案。开发者通过集成客户端SDK和云端OPEN API,即可快速实现包含IM、音视频通话、直播、点播、互动白板、短信等功能。