MySQL

MySQL之隔离级别

作者头像 刘宇帅
2922 0

事务隔离性(isolation)

数据库中多个事务并发执行时是相互独立的,一个事务不会影响另外一个事务的执行,事务与事务的隔离是有不同级别的,不同级别情况下事务之间的影响不一样。
不同的事务隔离性会给数据库带来问题

不隔离的事务问题

脏读

脏读是指一个事务执行过程中读取到其他事务更新的数据,但是更新数据的事务最后有因为各种原因回滚了,这种想象叫做脏读,因为第一个事务查到的是一个无意义的数据。脏读举例如下

// 事务1
start transaction;
update students set sex=2 where name='洛基';
rollback;

// 事务2
select * from students where name='洛基';

如上事务1我们把洛基变成男性,而当事务1执行第二条语句后,事务2正好执行第一条,那么事务2查到的洛基性别如果2,这种现象叫做脏读。

不可重复读

不可重复读是指一个事务内的多次查询同一条数据却返回了不同的结果,这是因为在查询过程中数据被另外一个事务修改了。还以修改 洛基 性别为例子

//事务1
start transaction;
select * from students where name='洛基';
select * from students where name='洛基';
commit;

// 事务2
start transaction;
update students set sex=2 where name='洛基';
commit;

当事务1执行第1条 select 语句返回的洛基性别为 女,而在事务1执行第二条 select 语句前 事务2 正好开始执行别提交了,那么事务1的第二个 select 语句查询返回的洛基性别为 男。这种再同一个事务中统一个语句查询相同的数据却返回了不同的值得想象叫做不可重复读。

幻读

幻读是指一个事务内多次查询同一条件下的数据返回的行数不一致,这是因为在同一事务多次查询中有其他事务插入了新的数据导致的。我们以新同学入职为例

// 事务1
start transaction;
select * from students;
select * from students;
commit;

// 事务2
start transaction;
insert into students (`no`,`name`,`sex`,`age`) values ('0006','铁蛋',2,25);
commit;

当事务1执行第一条 select 语句时返回了4条语句,而在事务1执行第二条 select 语句前 事务2正好插入了一条新的数据并提交,那么当事务1第二个 select 执行时返回的数据为 5 条,就如同幻觉一般突然多了一条,这种现象叫做幻读。
不可重复读和幻读的都是两次读取到的数据不一致,但是不可重复读针对的是单条数据而幻读是指多条数据的新增和更新,不可重复读可通过锁行解决而幻读一般需要表锁解决(MySQL通过MVCC解决)。

数据库定义了四种事务隔离级别

READ UNCOMMITTED 未提交读、读未提交

未提交读就是事务再未提交的时候的修改的数据是可以别其他事务读取的,这就是上面的脏读的问题。这种隔离级别是最低的隔离级别。几乎所有数据库不会使用该级别的隔离。

READ COMMITTED 提交读、读已提交

读已提交级别满足数据库事务的基本隔离特性:一个事务的数据再未提交之前是不可见的,只有事务提交后数据才能被其他事务读取。读已提交可以解决读未提交的脏读问题,读已提交又叫做不可重复读,即读已提交存在上面提到的不可重复读的问题。大部分数据库的默认级别是 读已提交,但是MySQL不是。

REPEATABLE READ 可重复读

可重复读保证在一个事务中的多次查询相同条件下的数据返回的结果是一致的,也就是解决了读已提交的不可重复读和脏读的问题。但是可重复读无法解决幻读的问题。可重复读是 MySQL 的默认隔离级别,MySQL 的 InnoDB 和 XtraDB 存储引擎使用多版本并发控制(MVCC multiversion concurrency control)解决幻读问题。

SERIALIZABLE 可串行化

可串行化是最高的隔离级别,它通过强制事务串行执行避免前面的幻读问题。可串行化会在读取的每一行数据上都枷锁,所以会非常影响系统的性能,一般情况下没有业务会选择可串行化的隔离级别。

MySQL 默认事务级别

MySQL 默认使用可重复读事务级别并解决了幻读的问题,我们一下验证脏读、不可重复读、幻读的问题。
首先我们开启事务1并查询洛基性别

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from students where id=1;
+----+------+--------+-----+------+---------------------+---------------------+
| id | no   | name   | sex | age  | created_at          | updated_at          |
+----+------+--------+-----+------+---------------------+---------------------+
|  1 | 0001 | 洛基   |   1 |   25 | 2019-12-20 20:00:00 | 2019-12-20 01:16:41 |
+----+------+--------+-----+------+---------------------+---------------------+
1 row in set (0.00 sec)

然后我们在新的连接里更新洛基性别为男

mysql> update students set sex=2 where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

我们再在事务1里查询洛基性别,如下依然为女,这里就可以看出MySQL解决了不可重复读的问题

mysql> select * from students where id=1;
+----+------+--------+-----+------+---------------------+---------------------+
| id | no   | name   | sex | age  | created_at          | updated_at          |
+----+------+--------+-----+------+---------------------+---------------------+
|  1 | 0001 | 洛基   |   1 |   25 | 2019-12-20 20:00:00 | 2019-12-20 01:16:41 |
+----+------+--------+-----+------+---------------------+---------------------+
1 row in

而后我们在事务1里更新洛基性别为男

mysql> update students set sex=2 where id=1;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1  Changed: 0  Warnings: 0

我们再在新的连接里查询洛基性别,如下依然为女,这就可以看出MySQL解决了脏读的问题。

mysql> select * from students where id=1;
+----+------+--------+-----+------+---------------------+---------------------+
| id | no   | name   | sex | age  | created_at          | updated_at          |
+----+------+--------+-----+------+---------------------+---------------------+
|  1 | 0001 | 洛基   |   1 |   25 | 2019-12-20 20:00:00 | 2019-12-20 01:16:41 |
+----+------+--------+-----+------+---------------------+---------------------+
1 row in set (0.00 sec)

我们再在事务1里查询所有的学生,如下

mysql> select * from students;
+----+------+--------+-----+------+---------------------+---------------------+
| id | no   | name   | sex | age  | created_at          | updated_at          |
+----+------+--------+-----+------+---------------------+---------------------+
|  1 | 0001 | 洛基   |   1 |   25 | 2019-12-20 20:00:00 | 2019-12-20 01:16:41 |
|  2 | 0002 | 冷锋   |   2 |   25 | 2019-12-20 20:00:00 | 2019-12-20 20:00:00 |
|  3 | 0003 | 六一   |   2 |   25 | 2019-12-20 20:00:00 | 2019-12-20 20:00:00 |
|  4 | 0004 | 杨过   |   2 |   25 | 2019-12-20 00:26:14 | NULL                |
|  6 | 0006 | 铁蛋   |   2 |   25 | 2019-12-20 01:51:46 | NULL                |
+----+------+--------+-----+------+---------------------+---------------------+
5 rows in set (0.00 sec)

而后我们再新的连接里入学一个新同学,阿末

mysql> insert into students (`no`,`name`,`sex`,`age`) values ('0007','阿末',1,25);
Query OK, 1 row affected (0.00 sec)

我们再在事务1里查询所有学生,如下依然不包含阿末,这就可以看出MySQL解决了幻读的问题。

mysql> select * from students;
+----+------+--------+-----+------+---------------------+---------------------+
| id | no   | name   | sex | age  | created_at          | updated_at          |
+----+------+--------+-----+------+---------------------+---------------------+
|  1 | 0001 | 洛基   |   1 |   25 | 2019-12-20 20:00:00 | 2019-12-20 01:16:41 |
|  2 | 0002 | 冷锋   |   2 |   25 | 2019-12-20 20:00:00 | 2019-12-20 20:00:00 |
|  3 | 0003 | 六一   |   2 |   25 | 2019-12-20 20:00:00 | 2019-12-20 20:00:00 |
|  4 | 0004 | 杨过   |   2 |   25 | 2019-12-20 00:26:14 | NULL                |
|  6 | 0006 | 铁蛋   |   2 |   25 | 2019-12-20 01:51:46 | NULL                |
+----+------+--------+-----+------+---------------------+---------------------+
5 rows in set (0.00 sec)

注: MySQL 默认会对update的数据加锁,所以在事务中更新的数据行会别锁直到事务提交或回滚。

MySQL对其他隔离级别的支持

MySQL 支持 SQL 标准定义的四种隔离级别,默认使用可重复读隔离级别。使用如下语句切换隔离级别

set session transaction isolation level read committed;
set session transaction isolation level read committed;
set session transaction isolation level repeatable read;
set session transaction isolation level repeatable read;
作者头像

刘宇帅

非著名程序员,全栈开发工程师,长期专注系统开发与架构设计。

提示

功能待开通!


暂无评论~

相关文章

MySQL之创建库

MySQL 创建库语法 CREATE {DATABASE | SCHEMA} [IF NOT EXISTS] db_name [create_specification] ... create_specification: [DEFAULT] CHARACTER SET [=] charset_name | [DEFAULT] COLLATE [=] collation_name 创建一个默认的数据库 create database school; 执行结果 mysql> create database school; Query OK, 1 row affecte

MySQL之创建表

创建表 创建学生表 SQL create table if not exists`students` ( `id` int not null auto_increment comment '学生id', `no` char(5) not null comment '学生学号', `name` varchar(128) not null default '' comment '学生姓名', `sex` tinyint not null default 0 comment '0 无 1 女 2 男', `age` tinyint null default

MySQL之删除数据

单表删除语法 DELETE [LOW_PRIORITY] [QUICK] [IGNORE] FROM tbl_name [[AS] tbl_alias] [PARTITION (partition_name [, partition_name] ...)] [WHERE where_condition] [ORDER BY ...] [LIMIT row_count] 多表删除 DELETE [LOW_PRIORITY] [QUICK] [IGNORE] tbl_name[.*] [, tbl_name[.*]] ... FROM table_r

MySQL 技术内幕阅读笔记(0)--- InnoDB 体系结构和存储引擎

MySQL 体系结构 从图我们可以看出,MySQL 有如下几部分组成: 连接池组件 管理服务和工具组件 SQL 接口组件 查询优化组件 优化器组件 缓冲组件 插件式存储引擎 物理文件 需要注意存储引擎是基于表的而不是库,比如我们可以在建表的时候为表指明存储引擎。 MySQL 存储引擎 常见存储引擎 InnoDB MySQL 5.5.8 版本之后的默认存储引擎,事务性,行锁,支持外键。 MyISAM 存储引擎不支持事务、表锁,支持全文索引,主要面对一些 OLAP 数据库应用。MySQL 5.5.6 版本之前的默认存储引擎。 NDB 存储引擎是一个集群存储引擎。 Memory 存储引擎将表

查看 MySQL 表的大小

information_schema MySQL 自带一个数据库 information_schema ,这个数据库用于记录 MySQL 数据库的基本信息,比如数据库名,数据库的表,表栏的数据类型与访问权限等。 mysql> use information_schema; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql>