我们知道数据库事务有四种性质, 而数据库为了维护这些性质, 一般使用加锁的方式 , 但是数据库是一个高并发的应用,如果加锁不合理的话,会极大的影响数据库的并发能力,所以这篇文章带大家深入的了解MySQL的锁与事务隔离级别。
MySQL事务特性
原子性(Atomicity): 事务是一个原子操作单元 ,要么全部执行,要么全部都不执行。
一致性(Consistent):一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也不会丢失提交事务的操作。
MySQL事务隔离级别
隔离级别 | 脏读(Dirty Read) | 不可重复读(NonRepeatable Read) | 幻读(Pbantom Read) |
---|---|---|---|
读未提交(Read uncommitted) | 可能 | 可能 | 可能 |
读已提交(Read committed) | 不可能 | 可能 | 可能 |
可重复读(Repeatable Read) | 不可能 | 不可能 | 可能 |
可串行化(Serializable) | 不可能 | 不可能 | 不可能 |
未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据。
提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)。
可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读。
串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。
Read Uncommitted一般数据库都不会用,而且任何操作不会加锁,Serializable的并发能力太低,一般也不会使用,所以接下来介绍下Read Committed和Repeated Read这两种事务隔离级别。
我们首先在mysql建立一张表
然后随便插入几条数据
Read Committed
RC级别中,数据的读取都是不加锁的,但是数据的写入、修改和删除是需要加锁的。
例如:
我们开启A会话并设置事务隔离级别为RC:
然后我们更新一条数据。
同样的我们开启B会话并设置事务隔离级别为RC,并执行同样的更新SQL:
可以看到,B会话的更新sql会卡住 ,这是因为A会话在执行更新SQL时加了一个行锁 (X锁,排它锁),
其他会话只可以读,不能更新和删除,直到超时或A会话提交才结束。加了行锁是因为salary是有索引的,那如果update 语句 的where 列没有索引会是怎么样呢?
我们重新再来一遍,并执行步骤1:
执行update语句并以name列作为where条件(A会话):
在B会话执行同样where条件的话,同样会卡住:
允许更新其它行:
从表面上来看,其实和上面以索引列作为条件进行更新条件加行锁一样,但是本质上是有区别的。实际上在MySQL运行update语句时,它会改表的所有记录加锁,因为它不知道有多少个 name='zs'的记录,而真正的运行结果是只有 name ='zs' 的行加了锁,是因为MySQL server做了一些优化,当发现不满足where条件的行,会执行unlock row方法,把不满足条件的行释放锁。所以在对一个数据量大的表进行批量修改的时候,如果没有办法利用上索引,MySQL在过滤数据的时候会特别慢,就会出现没有更新某些行的数据,但是它们还是会被锁住的现象。
RC事务隔离级别解决了脏读问题,但是可能出现不可重复读和幻读问题。
例如:
我们在A 会话中设置事务隔离级别为RC后,进行如下操作:
然后再B会话中设置事务隔离级别为RC后,进行如下操作:
由此可以看出RC级别解决了数据的脏读问题
A会话进行commit:
B会话重新查询:
由此可以看出RC级别可能出现数据的不可重复读的问题。
Repeated Read
RR事务隔离级别解决了脏读问题和不可重复读问题,但是可能出现幻读问题
例如:
开启两个会话A、B,并设置事务隔离级别为RR:
当前表格记录为:
A会话执行更新语句:
B会话查询记录:
A会话提交:
再次查询B会话:
由此可见,RR事务隔离级别解决了脏读和不可重复读的问题,是可以进行重复读的。
不可重复读和幻读的区别
不可重复读重点在于update和delete。
而幻读的重点在于insert。
如果使用锁机制来实现这两种隔离级别,在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。
MySQL 锁
行锁
行锁就是针对数据表中行记录的锁。比如事务A更新了一行,而这时候事务B也要更新同一行,则必须等事务A的操作完成后才能进行更新。
间隙锁
是Innodb在RR事务隔离级别提交下为了解决幻读问题时引入的锁机制,读的问题存在是因为新增或者更新操作,这时如果进行范围查询的时候(加锁查询),会出现不一致的问题,这时使用不同的行锁已经没有办法满足要求,需要对一定范围内的数据进行加锁,间隙锁就是解决这类问题的。在可重复读隔离级别下,数据库是通过行锁和间隙锁共同组成的(next-key lock),来实现的。例如: 事务A执行for update 查询:
select * from company where salary>3000 for update;
复制
innodb会给所有salary>3000进行加锁。
注意: 间隙锁只会存在于RR事务隔离级别下,是为了解决幻读问题的。
简单的说:
行锁(Record Lock): 单条索引记录行加锁。
间隙锁(Gap Lock): 锁定索引记录间隙,确保索引记录的间隙不变。间隙锁是针对事务隔离级别为可重复读或以上级别的。
Next-Key锁: Record Lock + Gap Lock
共享锁与排他锁
InnoDB 实现了以下两种类型的行锁:
共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB 还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁:
意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁。
意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的 IX 锁。
总结
以上就是MySQL的事务隔离与锁的简单介绍与使用了,后面会在其他的文章里实战介绍各种锁怎么样实现,如何优化。
查看MySQL锁情况
select * from performance_schema.data_locks \G;
复制
参考资料
https://www.cnblogs.com/yufeng218/p/12547946.html
https://www.cnblogs.com/qishanmozi/p/e972c92103cf3735872d88657a013df9.html
https://tech.meituan.com/2014/08/20/innodb-lock.html
https://blog.csdn.net/qq_40378034/article/details/90904573
https://zhuanlan.zhihu.com/p/29150809