数据库事务隔离级别

在并发的场景中,为了保证数据的一致性我们会在数据库中使用事务。然而在强一致性与性能上则需要根据具体业务来取舍,所以一般数据库提供了四种事务隔离级别:

  1. 读未提交(Read Uncommitted)
  2. 读提交(Read Committed)
  3. 可重复读(Repeatable Read)
  4. 序列化(Serializable)

由于日常工作中使用事务比较频繁,遂在此作一下总结

在了解这四种事务隔离级别之前,需要了解如下概念:

更新丢失(Lost Update): 两个事务同时修改一行数据,其中一个事务的更新被另外一个事务的更新覆盖了

脏读(Dirty Reads): 一个事务在修改(未提交)数据时,被另一个事务读到了修改后的数据

不可重复读(Non-repeatable Reads): 一个事务先读取了某个数据,然后另外一个事务修改(并提交)了数据后,这个事务再一次读取之前的数据时,得到的是不一样的值

幻读(Phantom Reads): 一个事务预先读取到或者未读取到某条数据,然后另外一个事务删除或新增这条数据(并提交)后,这个事务再一次读取之前的数据时未读到或读到数据。

以上概念对应在四种隔离级别中:

读未提交(Read Uncommitted) 在该级别下的事务允许脏读,但不允许更新丢失:当一个事务开始修改某个值时,其他的事务可以读这个值,但是无法修改这个值。

以mysql为例,将事务隔离级别设置为Read Uncommitted

mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)

测试表格式:

mysql> desc user;
+---------+------------------+------+-----+---------+----------------+
| Field   | Type             | Null | Key | Default | Extra          |
+---------+------------------+------+-----+---------+----------------+
| id      | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| name    | varchar(255)     | NO   |     | NULL    |                |
| balance | int(10) unsigned | NO   |     | 0       |                |
+---------+------------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

测试数据:

mysql> select * from user;
+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  1 | eslizn |     100 |
+----+--------+---------+
1 row in set (0.01 sec)

打开一个新的会话:

mysql> show processlist;
+----+------+-----------+------+---------+------+-------+------------------+
| Id | User | Host      | db   | Command | Time | State | Info             |
+----+------+-----------+------+---------+------+-------+------------------+
|  1 | root | localhost | test | Sleep   |   41 |       | NULL             |
|  2 | root | localhost | NULL | Query   |    0 | init  | show processlist |
+----+------+-----------+------+---------+------+-------+------------------+
2 rows in set (0.00 sec)

在会话1中使用事务修改数据,但不提交:

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

mysql> update user set balance = balance - 5 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

在会话2中查询(脏读):

mysql> select * from user;
+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  1 | eslizn |      95 |
+----+--------+---------+
1 row in set (0.00 sec)

在会话2中修改(不会更新丢失):

mysql> update user set balance = balance + 5 where id = 1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

因为会话1对该条数据的操作还未结束,所以无法得到写锁。

读提交(Read Committed) 在该级别下允许不可重复读,但不允许脏读。

回滚之前会话1中的操作,设置事务隔离级别为读提交:

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

mysql> select * from user;
+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  1 | eslizn |     100 |
+----+--------+---------+
1 row in set (0.00 sec)

mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)

在会话1中使用事务修改数据,但不提交:

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

mysql> update user set balance = balance - 5 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

在会话2中修改事务隔离级别为读提交并读取数据(不允许脏读):

mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user;
+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  1 | eslizn |     100 |
+----+--------+---------+
1 row in set (0.00 sec)

回滚会话1,新建事务读取内容:

mysql> rollback;
Query OK, 0 rows affected (0.01 sec)

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

mysql> select * from user;
+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  1 | eslizn |     100 |
+----+--------+---------+
1 row in set (0.00 sec)

在会话2中修改这个数据:

mysql> update user set balance = balance - 5 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

在会话1中再次读取(不可重复读):

mysql> select * from user;
+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  1 | eslizn |      95 |
+----+--------+---------+
1 row in set (0.00 sec)

读取到了与之前不一样的值。

可重复读(Repeatable Read) 禁止不可重复读,但可能出现幻读。

回滚会话1,设置隔离级别为可重复读:

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

mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)

在会话1的事务中读取数据:

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

mysql> select * from user;
+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  1 | eslizn |      95 |
+----+--------+---------+
1 row in set (0.00 sec)

在会话2中修改事务隔离级别,修改此数据:

mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)

mysql> update user set balance = balance - 5 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from user;
+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  1 | eslizn |      90 |
+----+--------+---------+
1 row in set (0.00 sec)

在会话1中查询(可重复读):

mysql> select * from user;
+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  1 | eslizn |      95 |
+----+--------+---------+
1 row in set (0.00 sec)

在会话2中新增一条数据:

mysql> insert into user (name, balance) values ('root', 100);
Query OK, 1 row affected (0.00 sec)

mysql> select * from user;
+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  1 | eslizn |      90 |
|  2 | root   |     100 |
+----+--------+---------+
2 rows in set (0.00 sec)

在会话1中查询(幻读):

mysql> select * from user;
+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  1 | eslizn |      95 |
+----+--------+---------+
1 row in set (0.00 sec)

序列化(Serializable) 在此级别下,修改数据的操作必须是位于事务队列的最前端(所以个人觉得叫做队列化更合适),事务化级别最高,但是带来的资源竞争也更多。

在会话1中设置事务级别并打开会话查询数据:

mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)

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

mysql> select * from user;
+----+--------+---------+
| id | name   | balance |
+----+--------+---------+
|  1 | eslizn |      90 |
|  2 | root   |     100 |
+----+--------+---------+
2 rows in set (0.00 sec)

在会话2中删除一条数据:

mysql> delete from user where id = 2;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

因为无法获得锁而操作失败,故不会造成幻读。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏鬼谷君

MySQL事务学习-->隔离级别

793
来自专栏乐沙弥的世界

PL/SQL --> INSTEAD OF 触发器

INSTEAD OF 触发器常用于管理编写不可更新的视图,INSTEAD-OF触发器必须是行级的。

622
来自专栏撸码那些事

【眼见为实】自己动手实践理解数据库READ UNCOMMITED && SERIALIZABLE

1325
来自专栏跟着阿笨一起玩NET

sql server根据表中数据生成insert语句

修正了表中的字段如果是SQL中的关键字(如Order)时,生成的脚本执行会出错的bug

411
来自专栏PhpZendo

使用 MySQL Scheduler 和 Event 周期性创建数据表

使用 MySQL Scheduler 和 Event 周期性创建数据表,下面提供的是按月建表计划任务及事件通过 ON SCHEDULE EVERY 1 MINU...

942
来自专栏服务端思维

服务端开发指南与最佳实战 | 数据存储技术 | MySQL(02) 存储引擎的 InnoDB 与 MyISAM 之争

作为 MySQL 数据库的两种主要的存储引擎,InnoDB 与 MyISAM 各有长处。

623
来自专栏idba

MySQL 各种SQL语句加锁分析

Locking read( SELECT ... FOR UPDATE or SELECT ... LOCK IN SHARE MODE),UPDATE以及DE...

732
来自专栏文渊之博

如果正确读取SQL Server中的扩展事件?

    SQL Server中使用扩展事件捕捉所需的信息后,可以选择存放的位置。比如说内存或文件中,但无论存在哪里,其本质都是一个大XML。因此在SQL Ser...

1839
来自专栏Jerry的SAP技术分享

面试问题 - 只用位操作在ABAP里实现a+b

算法描述参考我的SCN博客 Just for fun – Implement a + b using pure bitwise operation in ABA...

3755
来自专栏乐沙弥的世界

收集统计信息导致索引被监控

      对于索引的调整,我们可以通过Oracle提供的索引监控特性来跟踪索引是否被使用。尽管该特性并未提供索引使用的频度,但仍不失为我们参考的方式之一。然而...

552

扫码关注云+社区