待研究-闲聊mysql数据库事务隔离级别RR及RC

闲聊mysql数据库事务隔离级别RR及RC

概述

众所周知,除了MySQL默认采用RR隔离级别之外,其它几大数据库都是采用RC隔离级别。为啥mysql要这样设置呢?其实是MySQL为了规避一个数据复制场景中的缺陷,而选择 Repeatable Read 作为默认隔离级别。不过不同数据库实现方式还是不太一样。

例如Oracle仅仅实现了RC 和 SERIALIZABLE隔离级别。默认采用RC隔离级别,解决了脏读。但是允许不可重复读和幻读。其SERIALIZABLE则解决了脏读、不可重复读、幻读。

而MySQL默认采用RR隔离级别,SQL标准是要求RR解决不可重复读的问题,但是因为MySQL采用了gap lock,所以实际上MySQL的RR隔离级别也解决了幻读的问题。那么MySQL为什么要选择RR这个隔离级别呢?


一、数据库事务基础概念

事务处理之父Jim Gray对事务隔离性的定义[1]:

Isolation: Concurrently executing transactions see the stored information as if they were running serially (one after another).

并发执行的事务能够看到存储的信息,好似事务之间是串行执行的。这里看似还是太理论,但是只要解决三个并发问题,事务间就不会有环(cycle),因此就能达到真正隔离性的要求。而这三个问题Jim Gray将其总结为Lost Update、Read Uncommitted、Unrepeatable Read。

越高的隔离级别,能解决的数据一致性问题越多,理论上性能损耗更大,可并发性越低。隔离级别依次为

复制代码
SERIALIZABLE > RR > RC > Read-Uncommited

1、数据库事务ACID特性

数据库事务的4个特性:

  • 原子性(Atomic): 事务中的多个操作,不可分割,要么都成功,要么都失败; All or Nothing.
  • 一致性(Consistency): 事务操作之后, 数据库所处的状态和业务规则是一致的; 比如a,b账户相互转账之后,总金额不变;
  • 隔离性(Isolation): 多个事务之间就像是串行执行一样,不相互影响;
  • 持久性(Durability): 事务提交后被持久化到永久存储.

2、事务隔离性

其中 隔离性 分为了四种(注意是层层递进):

  • READ UNCOMMITTED:可以读取未提交的数据,未提交的数据称为脏数据,所以又称脏读。此时:幻读,不可重复读和脏读均允许;
  • READ COMMITTED:只能读取已经提交的数据;此时:允许幻读和不可重复读,但不允许脏读,所以RC隔离级别要求解决脏读;
  • REPEATABLE READ(真正符合隔离性的要求):同一个事务中多次执行同一个select,读取到的数据没有发生改变;此时:允许幻读,但不允许不可重复读和脏读,所以RR隔离级别要求解决不可重复读;
  • SERIALIZABLE: 幻读,不可重复读和脏读都不允许,所以serializable要求解决幻读;

3. 脏读、不可重复读以及可重复读概念

  • 脏读:可以读取未提交的数据。RC 要求解决脏读;
  • 不可重复读:同一个事务中多次执行同一个select, 读取到的数据发生了改变(被其它事务update并且提交);
  • 可重复读:同一个事务中多次执行同一个select, 读取到的数据没有发生改变(一般使用MVCC实现);RR各级级别要求达到可重复读的标准;
  • 幻读:同一个事务中多次执行同一个select, 读取到的数据行发生改变。也就是行数减少或者增加了(被其它事务delete/insert并且提交)。SERIALIZABLE要求解决幻读问题;

ANSI SQL标准没有从隔离程度进行定义,而是定义了事务的隔离级别,同时定义了不同事务隔离级别解决的三大并发问题:


二、MySQL 中RC和RR隔离级别的区别

MySQL数据库中默认隔离级别为RR,但是实际情况是使用RC 和 RR隔离级别的都不少。好像淘宝、网易都是使用的 RC 隔离级别。那么在MySQL中 RC 和 RR有什么区别呢?

1、RC 与 RR 在锁方面的区别

1)RR 支持 gap lock(next-key lock),而RC则没有gap lock。

因为MySQL的RR需要gap lock来解决幻读问题。而RC隔离级别则是允许存在不可重复读和幻读的。所以RC的并发一般要好于RR;

2)RC 隔离级别,通过 where 条件过滤之后,不符合条件的记录上的行锁,会释放掉(虽然这里破坏了"两阶段加锁原则");但是RR隔离级别,即使不符合where条件的记录,也不会是否行锁和gap lock;所以从锁方面来看,RC的并发应该要好于RR;另外 insert into t select ... from s where 语句在s表上的锁也是不一样的

2、RC 与 RR 在复制方面的区别

RC 隔离级别不支持 statement 格式的bin log,因为该格式的复制,会导致主从数据的不一致;只能使用 mixed 或者 row 格式的bin log; 这也是为什么MySQL默认使用RR隔离级别的原因。复制时最好使用:binlog_format=row。

3、 RC 与 RR 在一致性读方面的区别

RC隔离级别时,事务中的每一条select语句会读取到他自己执行时已经提交了的记录,也就是每一条select都有自己的一致性读ReadView; 而RR隔离级别时,事务中的一致性读的ReadView是以第一条select语句的运行时,作为本事务的一致性读snapshot的建立时间点的。只能读取该时间点之前已经提交的数据。

4、RC 支持半一致性读,RR不支持

RC隔离级别下的update语句,使用的是半一致性读(semi consistent);而RR隔离级别的update语句使用的是当前读;当前都会发生锁的阻塞。

而半一致性读可以减少了update语句时行锁的冲突;对于不满足update更新条件的记录,可以提前放锁,减少并发冲突的概率。


三、为什么MySQL将RR作为默认的隔离级别?选哪种级别?

关于MySQL数据库为什么要将RR作为默认的隔离级别?日常项目中应该选哪种隔离级别这个问题,这里就要从binlog开始说起了。

1、binary log 的主要用途

MySQL的 binary log 记录了数据库的"事件"。这些"事件"描述了数据库的改动,包括 表的创建、数据修改等。

binary log 主要用于:

  • 主从复制。主库 master 会将其 binary log 中的数据改动记录("事件")发送给 从库 slave。从库执行这些"事件"使数据与主库保持一致。
  • 数据恢复。

2、binary log 的三 种模式

binary log 有三种模式:

1)、STATEMENT

每一条会修改数据的sql都会记录在binlog中。

sql 复制代码
e.g:delete from t1 where id<=1000;

优点:不需要记录每一行的变化,减少了binlog日志量,节约了IO,提高性能。只需要记录在 master 上所执行的语句的细节,以及执行语句时候的上下文的信息。

缺点:由于记录的只是执行语句,为了这些语句能在slave上正确运行,因此还必须记录每条语句在执行的时候的一些相关信息,以保证所有语句能在slave得到和在master端执行时候相同的结果。像一些特定函数功能,slave可与master上要保持一致会有很多相关问题(如sleep()函数,rand()函数等会出现问题warning)。

2)ROW

不记录sql语句上下文相关信息,仅保存哪条记录被修改,也就是说日志中会记录成每一行数据被修改的形式,然后在 slave 端再对相同的数据进行修改。

ini 复制代码
eg:
delete from t1 where id=1;
delete from t1 where id=2;
......
delete from t1 where id=1000;

优点:binlog中可以不记录执行的sql语句的上下文相关的信息,仅需要记录那一条记录被修改成什么了。所以rowlevel的日志内容会非常清楚的记录下每一行数据修改的细节。而且不会出现某些特定情况下的存储过程,或function,以及trigger的调用和触发无法被正确复制的问题。

缺点:在 row 模式下,所有的执行的语句当记录到日志中的时候,都将以每行记录的修改来记录,这样可能会产生大量的日志内容。

3)MIXED

是以上两种level的混合使用,一般的语句修改使用statment格式保存binlog,如一些函数,statement无法完成主从复制的操作,则采用row格式保存binlog,MySQL会根据执行的每一条具体的sql语句来区分对待记录的日志形式,也就是在Statement和Row之间选择一种;

新版本的MySQL中对row模式也被做了优化,并不是所有的修改都会以rowl来记录,像遇到表结构变更的时候就会以statement模式来记录。至于update或者delete等修改数据的语句,还是会记录所有行的变更。

3、基于Statement的数据复制问题

上述两个事务的隔离级别都是 Read-Committed。

真实的执行顺序是 先删后插。而 binlog 中记录的 Statement 顺序为 先插后删。

当基于这份 Statement 格式的 binlog 进行主从复制或恢复数据时,得到的最终数据与实际情况不符。

4、针对数据复制问题的两种解决方法:

  • 选用 Row 模式 的 binlog针对实际数据行的记录当然可以准确反映改动历史
  • 选用 Repeatable-Read 事务隔离级别"可重复读"隔离级别下,delete、insert 等语句会对数据加 间隙锁。上述Session2中的insert语句将被阻塞,直到session1 commit;

因为早期MySQL只支持 Statement 格式的 binlog,所以只能采用 Repeatable-Read 隔离级别来规避数据不一致的风险。从 5.7.7 开始,MySQL的默认 binlog 格式已经是 ROW。

5、项目应该选哪种事务隔离级别?

大多数项目中可以选 Read-Committed(读已提交),既降低死锁几率,又能提升并发性能。同时选用 Row 模式的binlog。

当然,这类 "最终一致性"的事务解决方案可能无法满足某些特殊项目的需求,需要采用 Serializable (串行)的隔离级别

相关推荐
Asthenia04123 分钟前
Feign结构与请求链路详解及面试重点解析
后端
左灯右行的爱情6 分钟前
缓存并发更新的挑战
jvm·数据库·redis·后端·缓存
brzhang9 分钟前
告别『上线裸奔』!一文带你配齐生产级 Web 应用的 10 大核心组件
前端·后端·架构
shepherd11111 分钟前
Kafka生产环境实战经验深度总结,让你少走弯路
后端·面试·kafka
袋鱼不重23 分钟前
Cursor 最简易上手体验:谷歌浏览器插件开发3s搞定!
前端·后端·cursor
嘻嘻哈哈开森25 分钟前
Agent 系统技术分享
后端
用户40993225021226 分钟前
异步IO与Tortoise-ORM的数据库
后端·ai编程·trae
会有猫31 分钟前
LabelStudio使用阿里云OSS教程
后端
惜鸟32 分钟前
如何从模型返回结构化数据
后端
GZ25337 分钟前
Smart Input Pro使用教程
后端