一、事务的理解
查看数据库的事务配置:
mysql> show variables like 'transaction_isolation'; +-----------------------+----------------+ | Variable_name | Value | +-----------------------+----------------+ | transaction_isolation | READ-COMMITTED | +-----------------------+----------------+
1、什么是事务
事务是指一条或者一组语句 组成一个单元,这个单元要么都成功要么都失败。
例如:张三有1000元,李四有1000元;但是 张三给了李四400,那么李四就有1000,张三还剩 500;
update table user set money=500 where name = "张三";
update table user set money=1500 where name = "李四";
那么 我们肯定要保证这两条语句要么都成功,要么都失败。这时候就用到了事务。
2、事务的4大特性(ACID)
A(原子性 Atomicity):
原子性指事务是不可分割的,要么都执行成功,要么都失败;
C(一致性Consistency):
事务必须使得数据库状态从一个一致性状态,变为另一个一致性状态;
I(隔离性 Isolation):
指一个事务的执行,不被其他事务所影响;
D(持久性Durability):
持久性指一个事务一旦提交之后 对数据库饿修改是永久的;
二、创建一个事务
事务其实可以分为两大类:隐式事务 和 显示事务
隐式事务:SET IMPLICIT_TRANSACTIONS ON
比如我们 select、insert、update、delete 都是隐式事务;
显示事务
指带有明显开始和结束标记;
步骤一:禁用自动提交
set autocommit = 0;
步骤二:开启事务
start transaction;
步骤三:sql 语句
update table user set money=500 where name = "张三";
update table user set money=1500 where name = "李四";
步骤四:结束事务 --> 显示提交或回滚
commit 或 rollback;
自动提交事务:
指一旦执行 自动提交;
三、事务的隔离级别
上面说的事务,单个执行都不会有问题,但是当多个事务同时执行的时候,就会出现经典的三个问题,
脏读,幻读,不可重复读;
脏读
两个事务,事务A读取了一条事务B修改过的数据,事务B发现不对,我不能修改,进行了事务回滚,事务A 读到的就是无效的数据;
不可重复读:
两个事务,事务A多次读取同一条数据,事务B在事务A读取的过程中,不断的修改并提交这条数据,导致事务A读到的数据不一致;
幻读:
两个事务,事务A根据某个条件,读取了一些数据,事务B,插入或删除满足这个条件的数据,导致事务A再次读取的时候,发现一会变多一会变少;
总结: 从上面来看,很多人或混淆 不可重复读 和 幻读,其实这两个侧重点不同,不可能重复读 侧重的是 修改数据;而幻读侧重的是 新增或删除;
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交(read-uncommitted) | 是 | 是 | 是 |
不可重复读(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
mysql 默认隔离级别是:RR (repeatable- read),oracle默认是:RC(read-committed);
四、理解可重复读的实现
使用了MVVC 的控制方式,即Mutil-Version Concurrency Control,多版本并发控制,类似于乐观锁的一种实现方式。
实现方式:
InnoDB在每行记录后面保存两个隐藏的列来,分别保存了这个行的创建时间和行的删除时间。这里存储的并不是实际的时间值,而是系统版本号,当数据被修改时,版本号加1在读取事务开始时,系统会给当前读事务一个版本号,事务会读取版本号<=当前版本号的数据此时如果其他写事务修改了这条数据,那么这条数据的版本号就会加1,从而比当前读事务的版本号高,读事务自然而然的就读不到更新后的数据了
假设初始版本号为1:
INSERT
insert into user (id,name) values (1,'Tom');
id | name | create_version | delete_version |
1 | Tom | 1 |
下面模拟一下文章开头的场景:
SELECT (事务A)
select * from user where id = 1;
此时读到的版本号为1
UPDATE(事务B)
update user set name = 'Jerry' where id = 1;
在更新操作的时候,该事务的版本号在原来的基础上加1,所以版本号为2。
先将要更新的这条数据标记为已删除,并且删除的版本号是当前事务的版本号,然后插入一行新的记录
id | name | create_version | delete_version |
1 | Tom | 1 | 2 |
1 | Jerry | 2 |
SELECT (事务A)
此时事务A再重新读数据:
select * from user where id = 1;
由于事务A一直没提交,所以此时读到的版本号还是为1,所以读到的还是Tom这条数据,也就是可重复读
DELETE
delete from user where id = 1;
在删除操作的时候,该事务的版本号在原来的基础上加1,所以版本号为3
删除时,将当前版本号作为删除版本号
id | name | create_version | delete_version |
1 | Jerry | 2 | 3 |
参考文档:https://www.cnblogs.com/lmj612/p/10598971.html