事务原理
1)什么是事务
- 事务指的是逻辑上的一组操作,组成这组操作的各个单元要么全部成功,要么全部都失败。
- 事务作用:保证在一个事务中多次SQL操作要么全部成功,要么全都失败。
2)事务基本特性
(ACID
,是针对单个事务的一个完美状态)
- 原子性(Atomicity) : 原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发送,要么都不发生
- 一致性(Consistency) :事务前后数据的完整性必须保持一致。
保持一致性的工具:锁
- 隔离性(Isolation) : 事务的隔离性是指多用户的并发访问数据库时,一个用户的事件不能被其他用户的事务所干扰,多个并发事务之间数据互相隔离,隔离性由隔离级别保证!
- 正常情况下数据库是做不到完完全全的隔离,可以增强隔离级别,但是效率会非常底。
- read uncommitted -> read committed -> repeatable read -> serializable 【Mysql默认隔离级别RR】
- 事务并发问题:脏读、不可重复读、幻读
- 丢失更新的问题!
- MVCC:multiple version concurrency control(多版本并发控制)
- 持久性 :(Durability) : 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其他任何影响。
3)事务并发问题【事务隔离不足导致
如果不考虑隔离性,事务存在3中并发访问问题。
- 脏读:一个事务读到了另个一个事务未提交的数据
- 不可重复读:一个事务读到了另一个事务**已经提交(update)**的数据。引发另一个事务,在事务中的多次查询结果不一致。
- 虚读/幻读:一个事务读到了另一个事务已经**插入(insert)**的数据。导致另一个事务,在事务中多次查询的结果不一致。
4)事务隔离级别
数据库规范规定4种隔离级别,分别用于描述两个事务并发的所有情况。
- read uncimmitted 读未提交,一个事务读到另一个事务已经提交的数据。
- 存在:3个问题 (脏读、不可重复读、幻读)
- 解决:0个问题
- read committed 读已提交,一个事务读到另一个事务已经提交的数据。
- 存在:2个问题 (不可重复提交、幻读)
- 解决:1个问题 (脏读)
- repeatable read 可重复读,在一个事务读到的数据保持一直,无论另一个事务是否提交。
- 存在:1个问题 (幻读)
- 解决:2个问题 (脏读、不可重复读)
- serializable 串行化 同时只能执行一个事务,相当于事务种的单线程。
- 存在:0个问题
- 解决:3个问题 (脏读、不可重复读、幻读)
安全和性能对比
- 安全性:
serializable
>repeatable read
>read committed
>read uncimmitted
- 性能:
serializable
<repeatable read
<read committed
<read uncimmitted
常见数据的默认隔离级别:
-
Mysql:repeatable read
-
Oracle:read committed
手动提交
核心SQL语句
开始事务:start transaction
提交事务:commit
回滚事务:rollback
实现过程
第一步:开启事务
第二步:执行你的SQL语句
第三步:提交事务
事务案例
# 账户表
CREATE TABLE bankCount (
id int PRIMARY key auto_increment,
name VARCHAR(50),
money double
);
# 添加数据
insert into bankCount (name, money) VALUES ("ttw", 1000), ("qz", 2000);
#查看当前事务的隔离级别 - 当前会话的
SELECT @@tx_isolation;
#查看当前事务的隔离级别 - 全局的
SELECT @@global.tx_isolation;
#修改当前会话的隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
#修改全局的隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
- 设置数据库的隔离级别
set session transactionisolation level
级别字符串- 级别字符串:
READ UNCOMMITTED
、READ COMMITTED
、REPEATABLE READ
、SERIALIZABLE
- 读未提交:READ UNCOMMITTED
事务A | 事务B |
---|---|
设置B事务隔离级别【读未提交】 | 设置B事务隔离级别【读未提交】 |
开始A事务 | 开始B事务 |
查询 | |
更新数据 | |
再次查询:查询到B未提交数据【脏读】 | |
回滚 | |
再次查询:B未提交数据消失 | |
A事务提交【必须提交否则会对次测试产生影响】 | B事务提交 |
事务A
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
# 开启事务
START TRANSACTION;
SELECT * FROM bankCount;
# 脏读、不可重复读
# 当事务B执行后 B并未提交,但是A事务查询到的是B未提交的
# 提交事务 - 不提交会对下次测试产生影响,下次测试就不会有相同效果了。
COMMIT;
事务B
# 查看会话事务
SELECT @@tx_isolation;
# 设置会话事务
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
# 开启事务
START TRANSACTION;
SELECT * FROM bankCount;
UPDATE bankCount SET money=money+500 WHERE name='ttw';
# 回滚
ROLLBACK;
# 提交事务
COMMIT;
- 读已提交:read committed
事务A | 事务B |
---|---|
设置A事务隔离级别【读已提交】 | 设置A事务隔离级别【读已提交】 |
开始A事务 | 开始B事务 |
查询 | |
更新数据 | |
再次查询:数据没变,解决脏读问题 | |
B事务提交 | |
再次查询:数据没变,存在不可重复读问题 | |
A事务提交 |
事务A
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT @@tx_isolation;
# 开启事务
START TRANSACTION;
# 当再事务B中更改数据后 事务B可以查到为提交的事务A则查不到未提交的事务
SELECT * FROM bankCount;
事务B
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT @@tx_isolation;
# 开启事务
START TRANSACTION;
SELECT * FROM bankCount;
# 更新数据库bankCount的表数据
UPDATE bankCount SET money=money+500 WHERE name='ttw';
SELECT * FROM bankCount;
# 为提交的情况 ↑
# 提交事务 - 事务A就可以查到了
COMMIT;
- 可重复读:REPEATABLE READ
事务A | 事务B |
---|---|
设置A事务隔离级别【可重复读】 | 设置B事务隔离级别【可重复读】 |
开始A事务 | 开始B事务 |
查询 | |
更新数据 | |
再次查询:数据没变,解决脏读问题 | |
B事务提交 | |
再次查询:数据没变,解决不可重复读问题 | |
A事务提交 |
事务A
SELECT @@tx_isolation;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
# 开启事务
START TRANSACTION;
SELECT * FROM bankCount;
# 没提交前是读不到最新数据的
# 提交之后就可以读到新的数据了
COMMIT
事务B
SELECT @@tx_isolation;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
# 开启事务
START TRANSACTION;
SELECT * FROM bankCount;
# 更新数据库bankCount的表数据
UPDATE bankCount SET money=money+500 WHERE name='ttw';
COMMIT
- 串行话:SERIALIZABLE
事务A | 事务B |
---|---|
设置A事务隔离级别【串行化】 | 设置B事务隔离级别【串行化】 |
开始A事务 | 开始B事务 |
查询 | |
更新数据-提示等待,如果A没有进一步操作B将等待超时 | |
A事务提交或回滚 | |
B等待结束,执行操作 | |
B事务提交 |
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
# 开启事务
START TRANSACTION;
SELECT * FROM bankCount;
COMMIT
# 开启事务
START TRANSACTION;
# 更新数据库bankCount的表数据
UPDATE bankCount SET money=money+500 WHERE name='ttw';
# 只有事务 A 提交了 事务B才能更新或者查询
SELECT * FROM bankCount;