数据库事务隔离级别

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

  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 条评论
登录 后参与评论

相关文章

来自专栏pangguoming

Spring Boot集成JasperReports生成PDF文档

由于工作需要,要实现后端根据模板动态填充数据生成PDF文档,通过技术选型,使用Ireport5.6来设计模板,结合JasperReports5.6工具库来调用渲...

1.2K7
来自专栏张善友的专栏

Mix 10 上的asp.net mvc 2的相关Session

Beyond File | New Company: From Cheesy Sample to Social Platform Scott Hansel...

2577
来自专栏陈仁松博客

ASP.NET Core 'Microsoft.Win32.Registry' 错误修复

今天在发布Asp.net Core应用到Azure的时候出现错误InvalidOperationException: Cannot find compilati...

4848
来自专栏我和未来有约会

Silverlight第三方控件专题

这里我收集整理了目前网上silverlight第三方控件的专题,若果有所遗漏请告知我一下。 名称 简介 截图 telerik 商 RadC...

4025
来自专栏java 成神之路

使用 NIO 实现 echo 服务器

4617
来自专栏魂祭心

原 canvas绘制clock

4064
来自专栏C#

DotNet加密方式解析--非对称加密

    新年新气象,也希望新年可以挣大钱。不管今年年底会不会跟去年一样,满怀抱负却又壮志未酬。(不过没事,我已为各位卜上一卦,卦象显示各位都能挣钱...)...

4878
来自专栏张善友的专栏

Silverlight + Model-View-ViewModel (MVVM)

     早在2005年,John Gossman写了一篇关于Model-View-ViewModel模式的博文,这种模式被他所在的微软的项目组用来创建Expr...

2968
来自专栏杨龙飞前端

scrollto 到指定位置

2494
来自专栏我和未来有约会

Kit 3D 更新

Kit3D is a 3D graphics engine written for Microsoft Silverlight. Kit3D was inita...

2536

扫码关注云+社区