数据库事务和隔离级别的理解
1. 事务的概念
事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功。
2. MySQL数据库事务操作命令
直接通过例子来说明
- 事务开始但没有提交
start transaction事务开始
mysql> start transaction;
Query OK, 0 rows affected
mysql> update product set price=price+100 where id=3;
Query OK, 1 row affected
Rows matched: 1 Changed: 1 Warnings: 0
mysql> update product set price=price-100 where id=4;
Query OK, 1 row affected
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from product;
+----+----------+-------+-----+
| id | name | price | num |
+----+----------+-------+-----+
| 3 | nike | 988 | 2 |
| 4 | ads | 588 | 2 |
+----+----------+-------+-----+
当我直接关闭当前的dos窗口,没有提交事务的时候再次select会发现之前的操作没有成功。
mysql> select * from product;
+----+----------+-------+-----+
| id | name | price | num |
+----+----------+-------+-----+
| 3 | nike | 888 | 2 |
| 4 | ads | 688 | 2 |
+----+----------+-------+-----+
2.事务开始并提交
mysql> start transaction;
Query OK, 0 rows affected
mysql> update product set price=price+100 where id=3;
Query OK, 1 row affected
Rows matched: 1 Changed: 1 Warnings: 0
mysql> update product set price=price-100 where id=4;
Query OK, 1 row affected
Rows matched: 1 Changed: 1 Warnings: 0
mysql> commit;
Query OK, 0 rows affected
mysql> select * from product;
+----+----------+-------+-----+
| id | name | price | num |
+----+----------+-------+-----+
| 3 | nike | 988 | 2 |
| 4 | ads | 588 | 2 |
+----+----------+-------+-----+
3.回滚操作rollback(在commit之前才有效)
mysql> start transaction;
Query OK, 0 rows affected
mysql> update product set price=price+100 where id=2;
Query OK, 1 row affected
Rows matched: 1 Changed: 1 Warnings: 0
mysql> update product set price=price+100 where id=1;
Query OK, 1 row affected
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from product;
+----+----------+-------+-----+
| id | name | price | num |
+----+----------+-------+-----+
| 1 | 伊利 | 168 | 1 |
| 2 | 蒙牛 | 188 | 1 |
+----+----------+-------+-----+
mysql> rollback; 回滚
Query OK, 0 rows affected
mysql> select * from product;
+----+----------+-------+-----+
| id | name | price | num |
+----+----------+-------+-----+
| 1 | 伊利 | 68 | 1 |
| 2 | 蒙牛 | 88 | 1 |
+----+----------+-------+-----+
3. 事务的四大特性
1. 原子性(Atomicity)
事务是一个不可分割的单位,事务中的操作要么全部成功,要么全部失败。
2. 一致性(Consistency)
事务必须使数据库从一个一致性状态转变到另一个一致性状态;举个例子:A和B的总额为2000,无论他们之间如何转账,他们的总金额必须不变,这就是一致性。
3. 隔离性(isolation)
多个用户并发访问数据库时,数据库为每个用户开启事务,不会被其他事务操作所影响,多个并发事务操作互相隔离。这个隔离性有四种级别。
4. 持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
4. 并发事务可能出现的问题
- 查看当前会话隔离级别 select @@tx_isolation;
- 查看系统当前隔离级别 select @@global.tx_isolation;
- 设置当前会话隔离级别 set session transaction isolation level xx;
- 设置系统当前隔离级别 set global transaction isolation level xx;
- 查看是否自动提交:select @@autocommit; 1自动(默认),0:不自动
SQL语句的执行都是通过事务的提交来完成的,当事务创建的时候都会有一个与之对应的id,该id的分配是通过时序来的,也就是先创建的id小于后创建的id。
Mysql提供了多版本读取能力,什么叫多版本?就是说表中一份存储的数据行会有多个版本,这个版本对应的version就是transaction id。当行数据被某个transaction变更时, 这个行会有个隐含的hidden column中记录了这个更新者的transaction id。旧的数据在被覆盖的同时,会存储到一个undolog的文件中,这个文件中就是保存着每一行的版本数据链表。沿着这个链表我们可以看到这一行数据之前的版本变更。
快照读: 读取的是当前数据的可见版本(根据不同的隔离级别,所读取的可见版本是不同的)。普通的select语句。
当前读: 读取最新的记录版本。update、insert、delete这些操作执行之前都会进当前读,select … for update、select … lock in share mode。
4.1.脏读
事务A读取了事务B更新的数据,然后事务B回滚了,那么事务A读取的数据是脏数据,因为事务A读取了事务B未提交的数据。
4.2.不可重复读
事务A多次读取同一个数据,但是事务B在事务A的读取间隙更新并提交了数据,导致事务A读取结果不一致。
4.3.幻读
MySQL高性能这样写道:所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围内的记录时,会产生幻行。
这里的幻行是什么意思呢?并不是说第二次读取的时候多出来了新的记录,这样就成了不可重复读。
那么是如何证明有第二次查询有幻行出现:通过在读事务中插入新纪录的数据,会发现报冲突错误,但是我查询的时候并没有这条数据记录。通过写操作来证明有一条幻行记录。
通过图片可以看出即使事务A两次查询操作结果一样,但当插入id=3的这行数据时报主键冲突,这个就是幻行。
5.事务的隔离级别
5.1.读未提交(read uncommitted)
事务中的修改即使没有提交对其他事务也是可见的。事务可以读取为提交的数据,此时如果事务发生了回滚现象,那么其他事务读取的数据可能是无效数据,产生了脏读现象。
5.2.读已提交(read committed)
oracle数据库的默认隔离级别。一个事务开始到提交之前所做的任何修改对其他事务是不可见的。对于已经提交的事务所做的修改是可见的。
该隔离级别下发生不可重读的原因是:
该级别下的select语句会读取满足条件的最新的数据版本。
时间 | 事务A | 事务B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询余额1000 | |
T4 | 查询余额1000 | |
T5 | 取出1000,余额0 | |
T6 | 提交 | |
T7 | 查询余额0 | |
T8 | 提交 |
从上面我们可以看出事务A什么都没做 只是查询了两次数据,余额就从1000变成0,因为每次查询都会返回满足条件的最新数据版本(对于已经提交的事务所做的修改是可见的。)
5.3.可重复读(repeatable-read)(MySQL/InnoDB下的默认级别)
该级别保证在同一事务中多次读取同样的记录结果是一致的。有的文章说该隔离级别读事务会禁止其他事务修改该数据,所以解决了不可重复读的问题。个人觉得这个观点不正确。该隔离级别下解决不可重复读是因为同一事务中每次快照读取的都是同一版本数据,即使别的事务修改了该数据,读取的还是上一次的历史数据版本,所以才不会出现不可重复读的现象。
当一个事务进行快照读的时候,首先会在hidden column查询最新一个更新记录的transaction id,如果该id大于该事务的id,那么就去寻找旧版本,直到找到比当前事务id小的transaction id版本,返回该版本下符合条件的快照。并且在该事务的快照查询操作都会返回统一快照版本,即使有别的事务更新了该数据并且提交了事务。
5.4.串行化(serializable)
从MVCC并发控制退化为基于锁的并发控制。不区别快照读与当前读,所有的读操作均为当前读,读加读锁 (S锁),写加写锁 (X锁)。
Serializable隔离级别下,读写冲突,因此并发度急剧下降,在MySQL/InnoDB下不建议使用。
有关mysql加锁情况这篇文章有详细的解释:https://blog.csdn.net/qq_38238296/article/details/88362999
参考知乎扣鼎之歌的回答链接:https://www.zhihu.com/question/38507762/answer/968486962