乐趣区

重新认识Mysql之事务那些事

(一)什么是事务?

是一组原子性的 SQL 查询,或者说是一个独立的工作单元。事务内的语句,要么就全部执行,要么就全都不执行。

场景模拟:

用户 A 给用户 B 转账 1000 元

用户 A - 1000 元
用户 B + 1000 元

以上操作, 要么全部执行成功, 要么全部执行失败, 不会出现用户 A 扣钱, 用户 B 加钱这种现象发生.

(二、)事务的四大特性

  1. 原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
  1. 一致性(Consistency)
表示一个事务内有一个操作失败了,所有更改过的数据都必须会滚到修改前的状态
  1. 隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。即要达到这么一种效果:对于任意两个并发的事务 T1 和 T2,在事务 T1 看来,T2 要么在 T1 开始之前就已经结束,要么在 T1 结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。

4. 持久性(Durability)

持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

(三、)事务的隔离级别

数据库事务的隔离级别有 4 种,由低到高分别为 Read uncommitted(未提交读)、Read committed(读提交)、Repeatable read(重复读)、Serializable(序列化)。

但是在事务的并发操作中可能会出现脏读,不可重复读,幻读

1. Read uncommitted(未提交读)

引发现象:== 脏读 ==

事务 B 可以读取到事务 A 未提交的数据.

场景模拟:

职员小 A 的工资是 3000, 但是发工资时, 老板按错了数字, 按成了 30000 元, 但是事务还没有提交, 就在这个时候, 小 A 去查询了工资, 以为涨工资了非常开心, 但是老板发现不对, 及时回滚数据, 再重新改成 3000 进行提交:

场景分析:

这个情况就是脏读, 小 A 看到的工资是 30000 万, 但实际还是 3000 元, 因为他看到的是老板还没提交的数据.

解决方案:

Read committed(读提交), 能解决掉脏读的问题

2. Read committed(读提交)

事务 B 必须要等待事务 A 提交后才能读取数据,一个事务从开始到提交结束之前, 所做的任何修改对其他事务都是不可见的. 所以也称为
不可重复读

场景模拟:

职员小 A 准备去商场购物(卡余额 3000 元), 当他准备进行买单时(小 A 事务开启), 收费系统检测到小 A 的卡主有 3000 元可以支付, 就在这时, 小 A 的女朋友需要把钱全部转出去充当家用, 并提交(女朋友事务在小 A 前), 当收费系统准备扣款时, 再检测卡里的金额,发现已经没钱了, 结果小 A 购物失败 ….

场景分析:
这就是读提交, 事务 A 在对数据进行更新操作时, 事务 B 的读操作需要等待事务 A 更新操作提交完成后, 才能读取数据, 可以解决脏读问题。

但是在本场景中, 收费系统中一个事务范围, 两次相同的查询, 却获取到了不同的结果, 这就是 == 不可重复读(两次执行一样查询, 结果却不同)==

解决方案:

Repeatable read(重复读) 来解决.

3.Repeatable read(重复读)

重复读: 在事务开启进行读数据时, 不允许修改操作。

补充:Mysql 默认事务隔离级别为:Repeatable read

场景模拟:

第二个月, 职员小 A 又出去购物(卡里余额 3000 元), 当他准备买单时(事务开启, 不允许其他事务更新操作), 收费系统检测他卡中余额 3000 元, 他的女朋友又想转出这 3000 元作为家用, 但是转出失败(无法更新修改),收费系统扣款成功, 小 A 顺利完成了购物!

场景分析:

重复读, 虽然可以解决不可重读问题, 但是重复读对应的是修改 (Update) 操作, 还是可能中途会出现插入 (Insert) 操作, 会产生出幻读问题.

幻读场景模拟:

职员小 A, 去查询消费记录(事务开启), 发现消费了 1000 元, 卡中还剩 2000 元,开始打印消费清单(事务提交), 但是在打印清单的刚刚, 老板发工资了,新增了 3000 元(insert 了一条记录), 发现清单上的余额是 5000 元, 跟之前的查询的记录不对, 这就是幻读.

幻读:指的是一个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(phantom row)

重点:Mysql InnoDB 存储引擎通过 Next-Key Locking 解决了幻读的问题

解决方案:

非 InnoDB 引擎 幻读解决方案 Serializable(序列化)

Serializable(序列化)

Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。Serializable 在读取的每一行数据上都进行了加锁.

(四、事务操作命令及实战)

1. 查看数据库引擎是否支持事务(Innodb 支持?)

show engines

2. 查看 Mysql 默认储存引擎

show variables like '%storage_engine%';

3. 创建事务

// 开启事务
start transaction

// 开启事务
begin

// 二者等价

4. 提交事务

COMMIT 会提交事务,并使已对数据库进行的所有修改成为永久性的

// 提交
commit 

// 提交
commit work

// 二者等价

// 提交后开启一个新事务
commit and chain

5. 回滚事务

回滚会结束用户的事务,并撤销正在进行的所有未提交的修改

// 回滚
rollback

// 回滚
rollback work

// 二者等价

6.Mysql 自动提交模式

// 查看当前自动提交
show variables like 'autocommit'

// 禁止自动提交
SET AUTOCOMMIT=0  

// 开启自动提交
SET AUTOCOMMIT=1  

7. 事务还原点

// 在事务中创建一个保存点
savepoint identifier

// 将事务回滚到标记点
rollback to identifier

实战案例:

// 开启事务
begin;

// 更新
update users set name = '我是第一次的名字' where id = 2;

// 保存点 ts1
savepoint ts1;

// 更新
update users set name = '我是第二次的名字' where id = 2;

// 保存点 ts2
savepoint ts2;

// 查询
select * from users where id =2;

// 回滚到 ts1
rollback to ts1;

8.InnoDB 锁的问题

在 InnoDB 储存引擎中,start transaction 开启事务会造成一个隐式的 unlock tables 执行

实战案例:

ClientA:
    // 写锁
    lock table users write;

ClientB:
    // 查询, 卡住等待锁释放
    select name from users where id = 2 ;
    
ClientA:
    // 更新数据
    update users set name = '17ns' where id = 2;
    
ClientB:
    // 查询卡住中
    
ClientA:
    // 开启事务, 隐式执行了 unlock tables
    begin;
    
ClientB:
    // 获得数据

实战图

尾:

以上就是事务操作的基本性质主要特征就是 ACID(原子性, 一致性, 隔离性, 持久性), 在一些重要的场景使用事务会提高程序的可靠性, 使数据能够正确地提交到数据库当中。

退出移动版