当前位置: 首页 > 科技观察

MySQL为何使用可重复读(Repeatable Read)为默认隔离级别-

时间:2023-03-18 15:41:26 科技观察

为什么MySQL使用可重复读(RepeatableRead)作为默认的隔离级别?转载本文请联系JavaCollection公众号。群里有个小伙伴面试的时候,面试官问了一个很刁钻的问题:Mysql为什么默认使用可重复读(Repeatableread)作为隔离级别???进入正题:我们都知道事务的本质:原子性、一致性、隔离性和持久性(ACID)为了保持一致性和隔离性,一般使用锁来处理,但是锁相对降低了并发处理能力,数据库是一个高并发所以加锁的处理才是事务的本质。让我们看一下锁定协议以及事务在数据库中的作用。锁协议(LockingProtocol)MySQL的锁系统:共享锁和排它锁就是共享锁和排它锁,也叫读锁(S)和写锁(X),共享锁和排它锁都是悲观锁。排它锁可以分为行锁和表锁。LockingProtocol:当使用X锁或S锁来锁定数据时,一些约定的规则。比如什么时候申请X锁或者S锁,持续时间,什么时候释放锁等等Level1,level2,level3level级的阻塞协议对阻塞方式规定了不同的规则,形成了各种阻塞协议.不同的阻塞协议对并发操作的正确性提供不同程度的保证。在修改数据R之前,必须给它加一个X锁(排它锁),直到事务结束才会释放。事务结束包括正常结束(COMMIT)和异常结束(ROLLBACK)。第一级锁定协议防止丢失修改并保证事务T是可恢复的。丢失修改问题可以通过使用一级锁定协议来解决。在一级阻塞协议中,如果只是读取数据而不修改数据,则不需要加锁。它不能保证可重复读取,并且不会读取“脏”数据。二级阻塞协议二级阻塞协议的定义:一级阻塞协议加事务T在读取数据R前必须先给数据R加S锁(共享锁),读取完毕后释放S锁。交易的锁定和解锁严格分为两个阶段,第一阶段锁定,第二阶段解锁。加锁阶段:在任何数据读操作之前,申请并获得S锁(共享锁,其他事务可以继续加共享锁,但不能加排它锁),在执行写操作之前,申请并获得X锁(排它锁),其他事务不能再获取任何锁)。如果加锁不成功,则事务进入等待状态,直到加锁成功后才继续进行。解锁阶段:当事务释放锁后,事务进入解锁阶段,此时只能进行解锁操作,不能再进行加锁操作。除了防止丢失修改外,二级锁定协议还可以进一步防止读取“脏”数据。但是在二级阻塞协议中,由于S锁是在读取数据后释放的,所以不能保证可重复读。二次阻塞的目的是保证并发调度的正确性。也就是说,如果事务满足两阶段锁协议,那么事务的并发调度策略是串行的。事务的保证并发调度就是序列化(序列化很重要,尤其是数据恢复和备份的时候)三级阻塞协议三级阻塞协议的定义:一级阻塞协议在读取数据前加事务TRS锁(共享lock)必须先加进去,直到事务结束才会释放。在一级阻塞协议的基础上加S锁(一级阻塞协议:修改前加X锁,事务完成后释放),事务结束后释放S锁。三级阻塞协议防止修改丢失和不可读》除了数据之外,还进一步防止了不可重复读。以上三级协议的主要区别在于哪些操作需要申请封锁,什么时候申请四种事务隔离级别在数据库操作中,为了有效保证并发读取数据的正确性,提出了事务隔离级别。上面提到的阻塞协议也存在构建这些隔离级别。隔离级别脏读NonRepeatableRead幻读隔离级别脏读NonRepeatableRead幻读UncommittedReadpossiblePossiblemayhavesubmittedread(Readcommitted)Impossiblemaypossiblyrepeatableread(Repeatableread)Impossibleimpossiblepossibleserializable(Serializable)ImpossibleimpossibleimpossibleWhyisRR一般的DBMS系统会默认使用读提交(Read-Comitted,RC)作为默认的隔离级别,比如Oracle,SQLServer等,而MySQL使用的是可重复读(Read-Repeatable,RR)。要知道,隔离级别越高,越能解决数据一致性问题,理论上性能损失越大,并发越低。Theisolationlevelsare:SERIALIZABLE>RR>RC>RUWecansetandobtaintheisolationlevelofthedatabasethroughthefollowingstatement:Viewtheisolationlevelofthesystem:mysql>select@@global.tx_isolationisolation;+-----------------+|isolation|+-----------------+|REPEATABLE-READ|+-----------------+1rowinset,1warning(0.00sec)Viewtheisolationlevelofthecurrentsession:mysql>select@@tx_isolation;mysql>select@@tx_isolation;+----------------+|@@tx_isolation|+----------------+|READ-COMMITTED|+----------------+1rowinset,1warning(0.00sec)设置会话的隔离级别,隔离级别由低到高设置依次为:setsessiontransactonisolationlevelreaduncommitted;setsessiontransactonisolationlevelreadcommitted;setsessiontransactonisolationlevelrepeatableread;setsessiontransactonisolationlevelserializable;设置当前系统的隔离级别,隔离级别由低到高设置依次为:setglobaltransactonisolationlevelreaduncommitted;setglobaltransactonisolationlevelreadcommitted;setglobaltransactonisolationlevelrepeatableread;setglobaltransactonisolationlevelserializable;RepeatedRead:repeatableread.基于锁机制并发控制的DBMS,需要保持对选中对象的读锁和写锁,直到事务结束,但不需要“范围锁”,所以可能会出现“范围锁”幻读(phantomreads)"在这个事务级别下,保证同一个事务从头到尾获取的数据是一致的。是Mysql默认的事务级别。接下来我们思考两个问题,如果不可重复怎么办read问题出现在ReadCommitted级别?我需要解决吗?不需要解决,这个问题可以接受!毕竟你的数据已经提交了,读出来本身没有什么大问题!默认隔离Oracle和SqlServer的level是RC,我们没有改变它的默认隔离级别。在Oracle和SqlServer中,默认隔离级别是选择ReadCommitted(已提交读),为什么Mysql不选择ReadCommitted(已提交读)作为默认的iso隔离级别,而是选择可重复读(RepeatableRead)作为默认隔离级别?由于历史原因,早期Mysql(5.1版本之前)的Binlog类型Statement是默认格式,即依次记录系统接受的SQL请求;MySQL5.1及以后,提供了三种Binlog格式:Row、Mixed和statement。当binlog为statement格式,使用RC隔离级别时,会出现BUG,所以Mysql会把可重复读(RepeatableRead)作为默认的隔离级别!Binlog简介Mysqlbinlog是一个二进制日志文件,用于记录mysql的数据更新或潜在的更新(例如DELETE语句执行了删除但没有符合条件的数据),在mysql的主从复制中依赖它二进制日志。可以使用语句“showbinlogeventsin'binlogfile'”来查看binlog的具体事件类型。binlog记录的所有操作,实际上对应事件类型,MySQLbinlog的三种工作模式:Row(使用MySQL的特殊功能,如存储过程,触发器,函数,想最大化数据,选择Row模式,我们公司选择row)简介:日志会记录每一行数据的修改,然后在slave端修改相同的数据。优点:可以清楚的记录每一行数据修改的细节缺点:数据量太大语句(默认)简介:修改数据的每条sql都会记录在master的bin-log中,sql进程会在slave复制的时候解析,执行和原来master执行的sql一样的sql。主从同步一般不推荐使用语句模式,因为有些语句不支持,比如语句中包含UUID函数,LOADDATAINFILE语句等优点:解决了行级别的缺点,不需要记录每一行数据变化,减少bin-log日志量,节省磁盘IO,提升新性能。缺点:容易出现主从复制不一致的情况。我们可以简单的理解为binlog是一个记录数据库变化的文件。主从复制需要这个文件。具体细节先略过。**什么时候有bug?测试表:mysql>select*fromtest;+----+------+------+|id|name|age|+----+------+------+|1|NULL|NULL||2|NULL|NULL||3|NULL|NULL||4|NULL|NULL||5|NULL|NULL||6|NULL|NULL|+----+------+------+6rowsinset(0.00sec)Session1Session2mysql>settx_isolation='read-committed';查询正常,0行受影响,1条警告(0.00秒)mysql>settx_isolation='read-committed';查询正常,0行受影响,1个警告(0.00秒)开始;查询正常,受影响的0行(0.00秒)开始;查询正常,0行受影响(0.00秒)从测试中删除,其中1=1;查询正常,影响6行(0.00秒)insertintotestvalues(null,'name',100);查询正常,1行受影响(0.00秒)提交;查询正常,0行受影响(0.01秒)提交;查询正常,0行受影响(0.01秒)掌握此输出select*fromtest;+----+------+------+|id|name|age|+----+------+---+------+|7|name|100|+----+-----+------+1rowinset(0.00sec)但是此时你在执行from(slave)的语句,得到mysql>select*fromtest;Emptyset(0.00sec)的输出是在master上按照先删除后插入的顺序执行的!此时的binlog是STATEMENT格式的,是基于事务记录的。事务提交前先缓存二进制日志,提交后写入记录,所以顺序是先插入后删除!slave同步了binglog,所以slave的执行顺序和master不一致!从机插入后删除所有数据。有两种解决方法!(1)隔离级别设置为可重复读(RepeatableRead),在该隔离级别下引入间隙锁。当Session1执行删除语句时,间隙就会被锁定。那么,Ssession2就会阻塞插入语句的执行!(2)将binglog的格式改为行格式,此时是基于行复制的,自然不会出现sql执行顺序不同的问题!不过,这种格式是在mysql5.1版本开始引入的。因此,由于历史原因,mysql将默认的隔离级别设置为可重复读(RepeatableRead),以保证主从复制没有问题!RU和Serializable项目没有使用**未提交读(ReadUnCommitted)和序列化(Serializable)**两种隔离级别,原因:未提交读(ReadUnCommitted)允许脏读,即有可能读取未提交修改的数据其他会话中的事务。一个事务读取另一个事务未提交的读数据字符串Serializable使用的悲观锁理论实现起来简单,数据上更安全,但是并发能力很差。如果您的业务并发量很少或没有,需要及时可靠的数据,可以使用这种模式。一般在使用mysql的分布式事务功能时,会使用隔离级别RC和RR。这时候我们应该只有一个问题:隔离级别是readcommitted还是repeatableread?对比第一种情况:在RR隔离级别下,存在间隙锁,导致出现死锁的概率比RC高很多!实现一个简单的间隙锁示例select*fromtestwhereid<11;+----+------+-----+|id|name|age|+----+------+------+|1|NULL|NULL||2|NULL|NULL||3|NULL|NULL||4|NULL|NULL||5|NULL|NULL||6|NULL|NULL||7|名称|7|+----+------+------+7rowsinset(0.00sec)session1session2mysql>settx_isolation='repeatable-read';查询正常,0行受影响,1条警告(0.00秒)mysql>settx_isolation='repeatable-read';查询正常,0行受影响,1个警告(0.00秒)开始;select*fromtestwhereid<11forupdate;插入测试值(null,'name',9);//阻塞!通信它;QueryOK,0rowsaffected(0.00sec)QueryOK,1rowaffected(12.23sec)//释放锁后操作完成。RR隔离级别下,可以加锁gap(-∞,10],防止其他事务插入数据!RC隔离级别下,没有gap锁,其他事务可以插入数据!ps:RC隔离级别下,死锁也不是不可能,但是发生的概率比RR要低。表和行都被锁定在RR隔离级别下,如果条件列未命中索引,表就会被锁定!而在RC隔离级别下,只有行会被锁定select*fromtest;+----+------+------+|id|name|age|+----+------+------+|8|名字|11||9|名字|9||10|名字|15||11|名字|15||12|名字|16|+----+------+------+锁表示例:session1session2开始;更新测试集age=age+1whereage=15;行匹配:2更改:2警告:0插入测试值(null,'test',15);ERROR1205(HY000):超出锁定等待超时;提交;session2插入失败查询数据显示:select*fromtest;+----+-----+------+|id|name|age|+----+------+------+|8|名称|11||9|名称|9||10|名称|16||11|名称|16||12|名称|16|+----+------+------+半一致读(semi-consistent)特性在RC隔离级别下,半一致读(semi-consistent)特性增加了更新操作的并发!在5.1。15时,InnoDB引入了一个概念叫“半一致”,减少更新同一行记录时的冲突,减少锁等待。所谓半一致读,是指如果一个update语句读取了一行锁定的记录,此时InnoDB会返回最近提交的版本,判断这个版本是否满足where条件。如果满足,重新发起读操作。这个时候最新版本的行就会被读取并锁定!建议在RC层面,使用的binlog是行格式,基于行复制。Innodb的创始人也推荐binlog使用这个互联网项目请使用:ReadCommitted(读提交)这个隔离级别总结由于历史原因,老版本Mysql的binlog使用的是statement格式,如果RR隔离level不使用,会导致主从不一致。目前(5.1版本之后)我们使用行格式的Binlog可以通过RC隔离级别获得更好的并发性能。