mysql在项目中怎么选事务隔离级别


Posted in MySQL onMay 25, 2021

引言

开始我们的内容,相信大家一定遇到过下面的一个面试场景

面试官:“讲讲mysql有几个事务隔离级别?”
你:“读未提交,读已提交,可重复读,串行化四个!默认是可重复读”
面试官:“为什么mysql选可重复读作为默认的隔离级别?”
(你面露苦色,不知如何回答!)
面试官:"你们项目中选了哪个隔离级别?为什么?"
你:“当然是默认的可重复读,至于原因。。呃。。。”
(然后你就可以回去等通知了!)

为了避免上述尴尬的场景,请继续往下阅读!

Mysql默认的事务隔离级别是可重复读(Repeatable Read),那互联网项目中Mysql也是用默认隔离级别,不做修改么?
OK,不是的,我们在项目中一般用读已提交(Read Commited)这个隔离级别!

what!居然是读已提交,网上不是说这个隔离级别存在不可重复读幻读问题么?不用管么?好,带着我们的疑问开始本文!

正文

我们先来思考一个问题,在Oracle,SqlServer中都是选择读已提交(Read Commited)作为默认的隔离级别,为什么Mysql不选择读已提交(Read Commited)作为默认隔离级别,而选择可重复读(Repeatable Read)作为默认的隔离级别呢?

Why?Why?Why?

这个是有历史原因的,当然要从我们的主从复制开始讲起了!
主从复制,是基于什么复制的?

是基于binlog复制的!这里不想去搬binlog的概念了,就简单理解为binlog是一个记录数据库更改的文件吧~

binlog有几种格式?

OK,三种,分别是

  • statement:记录的是修改SQL语句
  • row:记录的是每行实际数据的变更
  • mixed:statement和row模式的混合

那Mysql在5.0这个版本以前,binlog只支持STATEMENT这种格式!而这种格式在读已提交(Read Commited)这个隔离级别下主从复制是有bug的,因此Mysql将可重复读(Repeatable Read)作为默认的隔离级别!
接下来,就要说说当binlog为STATEMENT格式,且隔离级别为读已提交(Read Commited)时,有什么bug呢?如下图所示,在主(master)上执行如下事务

mysql在项目中怎么选事务隔离级别

此时在主(master)上执行下列语句

select * from test;

输出如下

+---+
| b |
+---+
| 3 |
+---+
1 row in set

但是,你在此时在从(slave)上执行该语句,得出输出如下

Empty set

这样,你就出现了主从不一致性的问题!原因其实很简单,就是在master上执行的顺序为先删后插!而此时binlog为STATEMENT格式,它记录的顺序为先插后删!从(slave)同步的是binglog,因此从机执行的顺序和主机不一致!就会出现主从

不一致!

如何解决?

解决方案有两种!
(1)隔离级别设为可重复读(Repeatable Read),在该隔离级别下引入间隙锁。当Session 1执行delete语句时,会锁住间隙。那么,Ssession 2执行插入语句就会阻塞住!
(2)将binglog的格式修改为row格式,此时是基于行的复制,自然就不会出现sql执行顺序不一样的问题!奈何这个格式在mysql5.1版本开始才引入。因此由于历史原因,mysql将默认的隔离级别设为可重复读(Repeatable Read),保证主从复制不出问题!

那么,当我们了解完mysql选可重复读(Repeatable Read)作为默认隔离级别的原因后,接下来我们将其和读已提交(Read Commited)进行对比,来说明为什么在互联网项目为什么将隔离级别设为读已提交(Read Commited)!

对比

ok,我们先明白一点!项目中是不用读未提交(Read UnCommitted)和串行化(Serializable)两个隔离级别,原因有二

  • 采用读未提交(Read UnCommitted),一个事务读到另一个事务未提交读数据,这个不用多说吧,从逻辑上都说不过去!
  • 采用串行化(Serializable),每个次读操作都会加锁,快照读失效,一般是使用mysql自带分布式事务功能时才使用该隔离级别!(笔者从未用过mysql自带的这个功能,因为这是XA事务,是强一致性事务,性能不佳!互联网的分布式方案,多采用最终一致性的事务解决方案!)

也就是说,我们该纠结都只有一个问题,究竟隔离级别是用读已经提交呢还是可重复读?
接下来对这两种级别进行对比,讲讲我们为什么选读已提交(Read Commited)作为事务隔离级别!
假设表结构如下

CREATE TABLE `test` (
`id` int(11) NOT NULL,
`color` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB

数据如下

+----+-------+
| id | color |
+----+-------+
|  1 |  red  |
|  2 | white |
|  5 |  red  |
|  7 | white |
+----+-------+

为了便于描述,下面将

  • 可重复读(Repeatable Read),简称为RR;
  • 读已提交(Read Commited),简称为RC;

缘由一:在RR隔离级别下,存在间隙锁,导致出现死锁的几率比RC大的多!
此时执行语句

select * from test where id <3 for update;

在RR隔离级别下,存在间隙锁,可以锁住(2,5)这个间隙,防止其他事务插入数据!
而在RC隔离级别下,不存在间隙锁,其他事务是可以插入数据!

ps:在RC隔离级别下并不是不会出现死锁,只是出现几率比RR低而已!

缘由二:在RR隔离级别下,条件列未命中索引会锁表!而在RC隔离级别下,只锁行
此时执行语句

update test set color = 'blue' where color = 'white';

在RC隔离级别下,其先走聚簇索引,进行全部扫描。加锁如下:

mysql在项目中怎么选事务隔离级别

但在实际中,MySQL做了优化,在MySQL Server过滤条件,发现不满足后,会调用unlock_row方法,把不满足条件的记录放锁。

实际加锁如下

mysql在项目中怎么选事务隔离级别

然而,在RR隔离级别下,走聚簇索引,进行全部扫描,最后会将整个表锁上,如下所示

mysql在项目中怎么选事务隔离级别

缘由三:在RC隔离级别下,半一致性读(semi-consistent)特性增加了update操作的并发性!

在5.1.15的时候,innodb引入了一个概念叫做“semi-consistent”,减少了更新同一行记录时的冲突,减少锁等待。
所谓半一致性读就是,一个update语句,如果读到一行已经加锁的记录,此时InnoDB返回记录最近提交的版本,由MySQL上层判断此版本是否满足update的where条件。若满足(需要更新),则MySQL会重新发起一次读操作,此时会读取行的最新版本(并加锁)!
具体表现如下:
此时有两个Session,Session1和Session2!
Session1执行

update test set color = 'blue' where color = 'red';

先不Commit事务!
与此同时Ssession2执行

update test set color = 'blue' where color = 'white';

session 2尝试加锁的时候,发现行上已经存在锁,InnoDB会开启semi-consistent read,返回最新的committed版本(1,red),(2,white),(5,red),(7,white)。MySQL会重新发起一次读操作,此时会读取行的最新版本(并加锁)!
而在RR隔离级别下,Session2只能等待!

两个疑问

在RC级别下,不可重复读问题需要解决么?
不用解决,这个问题是可以接受的!毕竟你数据都已经提交了,读出来本身就没有太大问题!Oracle的默认隔离级别就是RC,你们改过Oracle的默认隔离级别么?

在RC级别下,主从复制用什么binlog格式?
OK,在该隔离级别下,用的binlog为row格式,是基于行的复制!Innodb的创始人也是建议binlog使用该格式!

总结

本文??锇肃铝艘黄?恼轮皇俏?怂得饕患?拢?チ??钅壳胗茫憾烈烟峤?Read Commited)这个隔离级别!

到此这篇关于mysql在项目中怎么选事务隔离级别的文章就介绍到这了,更多相关mysql 事务隔离级别内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

MySQL 相关文章推荐
详解MySQL主从复制及读写分离
May 07 MySQL
修改MySQL的默认密码的四种小方法
May 26 MySQL
Mysql中存储引擎的区别及比较
Jun 04 MySQL
MySQL REVOKE实现删除用户权限
Jun 18 MySQL
浅谈MySQL之select优化方案
Aug 07 MySQL
Mysql关于数据库是否应该使用外键约束详解说明
Oct 24 MySQL
分享mysql的current_timestamp小坑及解决
Nov 27 MySQL
MySQL数据库完全卸载的方法
Mar 03 MySQL
MySQL中rank() over、dense_rank() over、row_number() over用法介绍
Mar 23 MySQL
详解Mysql事务并发(脏读、不可重复读、幻读)
Apr 29 MySQL
Mysql将字符串按照指定字符分割的正确方法
May 30 MySQL
MySQL 原理与优化之Update 优化
Aug 14 MySQL
.Net Core导入千万级数据至Mysql的步骤
May 24 #MySQL
MySQL大小写敏感的注意事项
May 24 #MySQL
MySQL 使用事件(Events)完成计划任务
May 24 #MySQL
MySQL触发器的使用
May 24 #MySQL
MySQL 重命名表的操作方法及注意事项
May 21 #MySQL
Mysql官方性能测试工具mysqlslap的使用简介
May 21 #MySQL
MySQL官方导出工具mysqlpump的使用
May 21 #MySQL
You might like
全新的PDO数据库操作类php版(仅适用Mysql)
2012/07/22 PHP
PHP实现的博客欢迎提示功能(很特别哦)
2014/06/05 PHP
php实现可用于mysql,mssql,pg数据库操作类
2014/12/13 PHP
再谈Jquery Ajax方法传递到action(补充)
2014/05/12 Javascript
JavaScript中的style.cssText使用教程
2014/11/06 Javascript
JQuery判断checkbox是否选中及其它复选框操作方法合集
2015/06/01 Javascript
VUE2实现事件驱动弹窗示例
2017/10/21 Javascript
React Native 通告消息竖向轮播组件的封装
2020/08/25 Javascript
基于BootStrap的文本编辑器组件Summernote
2017/10/27 Javascript
js实现的订阅发布者模式简单示例
2020/03/14 Javascript
解决VUE自定义拖拽指令时 onmouseup 与 click事件冲突问题
2020/07/24 Javascript
vue中echarts的用法及与elementui-select的协同绑定操作
2020/11/17 Vue.js
微信小程序onShareTimeline()实现分享朋友圈
2021/01/07 Javascript
在Heroku云平台上部署Python的Django框架的教程
2015/04/20 Python
在Python中使用HTML模版的教程
2015/04/29 Python
python使用pycharm环境调用opencv库
2018/02/11 Python
Python cookbook(数据结构与算法)将序列分解为单独变量的方法
2018/02/13 Python
python实现人脸识别经典算法(一) 特征脸法
2018/03/13 Python
django2.0扩展用户字段示例
2019/02/13 Python
Python OS模块实例详解
2019/04/15 Python
Python3.5常见内置方法参数用法实例详解
2019/04/29 Python
python3实现mysql导出excel的方法
2019/07/31 Python
python 双循环遍历list 变量判断代码
2020/05/04 Python
详解torch.Tensor的4种乘法
2020/09/03 Python
CSS3中伪元素::before和::after的用法示例
2017/09/18 HTML / CSS
Spartoo葡萄牙鞋类网站:线上销售鞋履与时尚配饰
2017/01/11 全球购物
Camper鞋西班牙官方网上商店:西班牙马略卡岛的鞋类品牌
2019/03/14 全球购物
党委书记岗位职责
2013/11/24 职场文书
教育学专业实习生的自我鉴定
2013/11/26 职场文书
竞聘上岗演讲稿
2014/05/16 职场文书
毕业生找工作求职信
2014/08/05 职场文书
学校交通安全责任书
2014/08/25 职场文书
庆祝教师节主题班会
2015/08/17 职场文书
致运动员的广播稿
2015/08/19 职场文书
一文搞懂python异常处理、模块与包
2021/06/26 Python
nodejs利用readline提示输入内容实例代码
2021/07/15 NodeJs