参考:
数据库技术:MySql学习笔记之事务隔离级别详解
详解MySQL 数据库隔离级别与MVCC
MySQL 事务&&锁机制&&MVCC
数据库系统原理 – MySQL封锁
数据库常见面试题:乐观、悲观锁,行锁、表锁、读、写锁,间隙锁
MySQL中的锁(表锁、行锁)
数据库系统概念-读写锁、意向锁、行表锁
mysql的锁机制是悲观_一文带你了解 MySQL 中的各种锁机制!
每天一个知识点:“快照”在 MVCC 里是怎么工作的?
MVCC一致性视图 – 知乎
对于MVCC以及如何在RR上解决幻读的理解
MySQL的快照读(MVCC)和当前读(行锁、间隙锁、Next-Key Lock)解决幻读
前言
在对数据库进行操作的时候,为了保证一个业务流程中数据一致性(一组数据库操作,要么全部成功,要么全部失败),我们就需要使用事务来管理多个数据库操作。
在mysql中,事务支持是在引擎层实现的。mysql的原生引擎myisam并不支持事务,所以我们一般使用innodb。
事物作为一个不可分割的逻辑单元而被执行的一组sql语句,如果有必要,他们的执行效果可以被撤销。
对于事务,必须满足ACID四个特性(atomicity原子性、consistency一致性、isolation隔离性、durability持久性)。
隔离性概念
这里来看看隔离性,隔离性是指事务之间的并发是隔离的。在并发环境下,一个事务的执行过程中,可能会有并发的其他事务(sql)插入进来执行,破坏了事务的原子性,可能会对事务执行的结果产生影响。因此需要加锁来保证事务的正常执行,而加锁必然带来额外的开销,不同的加锁方式对执行效率有很大的影响,也衍生出了几种不同的隔离级别。
不同的隔离级别对事务之间的隔离性是不一样的(级别越高隔离性越好,但并发性能越差),上面也说过了其原理就是不同的加锁方式,这件事是数据库引擎比如innodb去帮我们做的,只是它屏蔽了加锁的细节。
数据库封锁
在介绍隔离级别之前,先来看看mysql的封锁机制。
行锁和表锁
在innodb引擎下,按锁的粒度分类,可以简单分为行锁和表锁。
行锁是作用在索引上的,当我们的sql命中了索引,那锁住的就是命中条件内的索引节点。如果没有命中索引,那我们锁的就是整个索引树(表锁)。
myisam只支持表锁,innodb支持表锁和行锁,默认为行锁。所以mysql的行锁一般是指在innodb引擎层实现的行级共享锁和行级排他锁。
mysql的表锁由MySQL Server实现,行锁则是存储引擎实现,不同引擎的实现方式不同。innodb引擎支持行锁,而myisam只能使用MySQL Server提供的表锁。
- 表锁粒度大,加锁开销小,锁冲突概率高,并发度低,加锁快,不会出现死锁。
- 行锁粒度小,加锁开销大,锁冲突概率小,并发度高,加锁慢,会出现死锁。
行锁由innodb管理,因为锁是逐步获得的(单个sql组成的事务除外),可能会发生死锁,innodb可以检测到死锁并使其中一个事务释放资源并rollback。但设计外部锁或表锁的情况下,innodb并不能完全检测死锁,可以设置锁超时等待参数innodb_lock_wait_timeout来解决。当然这个参数更多用于解决高并发下多个事务无法获得锁而挂起,大量占用计算机资源的问题。
读写锁
按照读写分类,锁可以分为读锁(共享锁、S锁)和写锁(排他锁、X锁)。读锁是共享的,多个事务可以同时读取同一个资源。写锁是排他的,写锁会阻塞其他的写锁和读锁。
意向锁
再来看下意向锁,意向锁(IS/IX)都是表级锁
如果事务想要给表中几行数据加上行级共享锁,那么需要先在表级别加上意向共享锁(IS);
如果事务想要给表中几行数据加上行级排他锁,那么需要先在表级别加上意向排他锁(IX)。
(注意:如果是想要加表级S锁或X锁,不需要先加意向锁。)
当向一个表添加表级X锁或者S锁时,如果没有意向锁的话,则需要遍历所有整个表判断是否有不兼容的行锁的存在,以免发生冲突。如果有了意向锁,只需要判断该意向锁与即将添加的表级锁是否兼容即可,因为意向锁的存在代表了有行级锁的存在或者即将有行级锁的存在,因而无需遍历整个表,即可获取结果。
乐观锁和悲观锁
乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段,是一种思想,而不是dbms中的锁机制。
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。dbms中的行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新之前会判断下在此期间其他事务有没有去更新这个数据,如果有更新那就回滚当前事务,可以使用版本号机制mysql乐观锁如何实现和CAS算法实现。
乐观锁适用于多读的应用类型,省去了锁的开销,加大了系统吞吐量。但如果经常发生冲突回滚,则开销较大。
三级封锁协议和两段锁协议
略
InnoDB的4种隔离级别
在innodb中,定义了4种隔离级别,下面我们由低到高简单介绍一下。
READ UNCOMMITED(读未提交)
最低的隔离级别,一个事务还没提交时,它做的变更就能被别的事务看到。
这种隔离级别下,读取数据的时候不受任何影响。即你甚至可以读取一个正在被其他事务修改的数据,想读就读,想改就改。这当然开销很小,但是会带来许多的问题,比如”脏读”。即读取到了正在修改但是却还没有提交的数据,这就会造成数据读取的错误。
从性能上来说,READ UNCOMMITED不会比其他级别好太多,但是却带来了非常多的麻烦的问题,因此在实际中很少使用。
对于锁的维度而言,其实就是在read uncommit隔离级别下,读不会加锁,而写会加排他锁。因为读没有任何锁,所以某个事务持有排他锁去更新数据对象的时候不会排掉读操作。
MVCC多版本并发控制
对于更新操作,innodb是肯定会加写锁的(数据库不可能允许在同一时间更新同一条记录)。而读操作,如果不加任何锁,就可能出现”脏读”。
脏读在生产环境下肯定是无法接受的,那如果读加锁的话,更新数据库时就没法读取了,这会极大地降低数据库性能。
对此,innodb引擎提供了一种新的方案来解决加锁后读写性能问题,就是MVCC(Multi-Version Concurrency Control)多版本并发控制
在MVCC下,就可以做到读写不阻塞,且避免了脏读这样的问题。
MVCC通过生成数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取
回到事务隔离级别下,针对于read commit(读已提交)隔离级别,它生成的就是语句级快照;而针对于repeatable read(可重复读),它生成的就是事务级快照。
快照:即一致性视图,在每个记录多版本的基础上,需要利用”一致性视图”,来做版本的可见性判断。
一致性视图主要是支持InnoDB在”读已提交”和”可重复读”级别的并发访问问题。
- “读已提交”级别下,会在 每个SQL开始执行的时候 创建一致性视图
- “可重复读”级别下,会在 每个事务开始的时候 创建一致性视图
READ COMMITED(提交读/不可重复读)
一个事务提交之后,它做的变更才会被其他事务看到。
这个级别在READ UNCOMMITED的基础上添加了一些规定,是一些数据库的默认隔离级别。它与READ UNCOMMITED的区别在于,它规定读取的时候读到的数据只能是提交后的数据。
思想很简单:innodb引擎下read commit(读已提交)是通过MVCC实现的,每一个sql语句执行前,都会重新算出一个新的一致性视图,快照读当前可见的数据(已提交的)。
read commit(读已提交)解决了脏读,但也会有其他并发问题。不可重复读:一个事务读取到另一个事务已经提交的数据,也就是说一个事务可以看到其他事务所作的修改。
这个级别所带来的问题就是不可重复读。即上一个时间读取到的a的值是1,但是随着修改线程对事务的提交,a的值变为了2,这时候读到的值就是2了,即执行两次相同的读取操作得到的值却不一样。一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读。
不可重复读同脏读的区别在于,脏读是一个事务读取了另一未完成的事务执行过程中的数据,而不可重复读是一个事务执行过程中,另一事务提交并修改了当前事务正在读取的数据。
REPEATED READ(可重复读)
REPEATED READ在READ COMMITED的基础上又添加了一些约束性的规则,它也是MySQL数据库的默认隔离级别。简单来说就是在一个事务的执行期间禁止其他事务对相应的数据进行修改,这就彻底使得一个事务的执行过程中所查询到的数据一定是一致的,即解决了脏读和不可重复读的问题,但是却带来了新的问题,即”幻读”。
“幻读”指的是在一个事务执行过程中虽然禁止了对相应数据的修改,但是其他的事务依然可以插入数据,这时候第一个事务就会发现会”莫名其妙”多出来一些数据,像是出现了幻觉似的。幻读和不可重复读都是读取了另一条已经提交的事务(这点同脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
在innodb引擎下,repeated read(可重复读)是通过MVCC实现的,在每个事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图做快照读。而当更新数据的时候,只能用当前读。
在innodb引擎下的repeated read(可重复读)隔离级别下,在MVCC下,快照读已经解决了幻读问题(因为它是读取历史版本数据);如果是当前读,innodb引擎使用MVCC+Next-key lock(同时添加间隙锁与行锁)解决幻读。
快照读sql
select ...;
当前读sql
-- 走的是IS锁(意向共享锁),即在符合条件的rows上都加了共享锁
select ... lock in share mode;
-- 走的是IX锁(意向排它锁),即在符合条件的rows上都加了排它锁
select ... for update;
update ...;
delete ...;
insert ...;
SERIALIZABLE (可串行化)
最高级别的隔离,对于同一行记录,”写”会加”写锁”,”读”会加”读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
这是最严格的一个隔离级别。它通过强制事务串行执行,避免了幻读的问题。但是这种隔离级别的开销极大,一般也不常使用。
原创文章,作者:carmelaweatherly,如若转载,请注明出处:https://blog.ytso.com/273374.html