MYSQL-初级-事务篇

概述

事务是一组操作集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起想系统提交或撤销请求,这么操作要么全部执行成功,要么全部失效

为什么有事务?

假设有一个银行数据库,张三向李四转账 1000 元。
image.png
这个转账操作涉及到两个步骤:

  1. 从张三的账户中扣除1000元。
  2. 在李四的账户中增加1000元。

如果没有事务,这两个步骤可能会分开执行,可能会出现以下问题:

  1. 第一步执行成功,张三 的账户减少了1000 元,但是第二步执行失败,李四 的账户没有增加1000 元。这样会导致数据不一致,张三 的钱消失了。
  2. 如果第一步执行失败,张三 的账户没有减少1000元,但是第二步执行成功,李四 的账户增加了1000元。这样也会导致数据不一致,李四 无缘无故多了1000 元。

解决办法:
使用事务可以解决这个问题,将上述两个步骤放在一个事务中。

  1. 如果第一步执行成功而第二步执行失败,那么整个事务会回滚,即第一步的操作也会被撤销,张三 的账户不会减少1000元。
  2. 同样,如果第一步执行失败,整个事务也会回滚,不会执行第二步。这样就可以确保数据的一致性。

事务操作

  1. 查看/设置事务提交方式
SHOW VARIABLES LIKE 'autocommit';

Variable_name|Value|
-------------+-----+
autocommit   |ON   |

-- 设置自动提交事务
SET autocommit = ON;
-- 设置手动提交事务
SET autocommit = OFF;
  1. 开启事务
-- 开始事务
BEGIN;

-- 执行一系列操作
UPDATE account SET balance = balance - 1000 WHERE account_id = 1;
UPDATE account SET balance = balance + 1000 WHERE account_id = 2;

-- 提交事务
COMMIT;

-- 如果发生错误,可以回滚事务
-- ROLLBACK;

事务的四大特性(AICD)

事务的四大特性,通常被称为ACID特性,是数据库管理系统保证数据一致性和可靠性的基础。这些特性分别是:

原子性(Atomicity)

事务中的所有操作要么全部完成,要么全部不完成,不会处于中间状态。如果事务在执行过程中发生错误,系统会回滚到事务开始前的状态,就像事务从未执行过一样。

  1. 从张三的账户中扣除1000元。
  2. 在李四的账户中增加1000元。

两个步骤要么全部完成,要么全部失效。

一致性(Consistency)

事务必须使数据库从一个一致性状态转移到另一个一致性状态。这意味着事务执行结束后,数据库的完整性约束没有被破坏,数据保持一致。

转账后,张三和李四的账户总额 == 转账前,张三和李四的账户总额。

隔离性(Isolation)

事务的隔离性意味着一个事务的执行不应该受到其他事务的干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

持久性(Durability)

一旦事务被提交,它对数据库的更改就是永久性的。即使系统发生崩溃,事务的结果也不会丢失,因为它们已经被持久化到数据库中。
这些特性共同保证了数据库事务的可靠性,即使在并发访问和系统故障的情况下,也能保持数据的一致性和完整性。

完成转账后,张三和李四的账户数据被持久存储到磁盘。


并发事务问题

脏读(Dirty Read)

一个事务读取了另一个未提交事务修改过的数据。如果这个未提交的事务最终回滚,那么第一个事务读取的数据就是无效的。
image.png

  1. bilibli 开启事务 A,查看了张三的账户余额还有 2000 元,自动扣款年度大会员 200 元。
  2. 张三开启事务 B,查看账户余额还有 1800 元。
  3. 此时,bilibili 发生异常,自动口矿失败,事务回滚。
  4. 张三之前查看的账户余额数据就是无效的。

假设我们已经解决了脏读的问题。

不可重复读

事务 A 两次读取同一行数据,但在两次读取之间,事务 B 修改了这行数据。因此,事务 A 在两次读取中得到了不同的结果。
主要针对 updatedelete 操作。
image.png

  1. 张三开启事务 A,第一次查看账户余额 2000 元。
  2. bilibili 开启事务 B 自动扣款年费大会员 200 元,并执行了 commit成功提交了事务。
  3. 张三再次读取余额,发现账户余额只有 1800 元,两次查看发现余额不同。

幻读

事务 A 在数据库中执行查询两次,但在两次查询之间,事务 B 插入了一些满足查询条件的新行。因此,事务 A 在两次查询中得到了不同的结果集。
主要针对 insert 操作。
image.png

  1. bilibli 管理员开启事务 A,查询用户账号信息,发现没有 Boss 的账户。
  2. 此时,Boss 开启事务 B,并成功注册了账户。
  3. 此时 bilibli 管理员想新增 Boss 账户时,发现新增失败。
  4. bilibili 管理员再次查询用户的账号信息,任然没有 Boss 的账户。

bilibli 管理员两次查询的用户数据不同,即幻读


事务隔离级别

隔离级别脏读不可重复读幻读
Read uncommitted(未提交读)
Read committed(已提交读)
Repeatable Read**(默认)**
Serializable

查看事务隔离级别

SELECT @@tx_isolation;
-- 或者(在MySQL 5.7.20及更高版本中)
SELECT @@transaction_isolation;

|@@transaction_isolation|
|-----------------------+
|REPEATABLE-READ        |

查看当前 Session 的事务隔离级别

  1. 将事务隔离级别设置为READ UNCOMMITTED
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
  1. 将事务隔离级别设置为READ COMMITTED
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
  1. 将事务隔离级别设置为REPEATABLE READ
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
  1. 将事务隔离级别设置为SERIALIZABLE
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

设置全局事务隔离级别

  1. 讲事务隔离级别设置为READ UNCOMMITED
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
  1. 将事务隔离级别设置为READ COMMITTED
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
  1. 将事务隔离级别设置为REPEATABLE READ
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
  1. 将事务隔离级别设置为SERIALIZABLE
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;

证明

现在有账户余额表:

id|name|salary|
--+----+------+
 1|张三  |  2000|

解决脏读问题

模拟场景
-- bilibli开启事务                           
START TRANSACTION;                       

-- 1. 查看张三用户余额                         
SELECT * FROM test_table tt;
|id|name|salary|
|--+----+------+
| 1|张三  |  2000|

-- 2. bilibli自动扣款
UPDATE test_table tt
SET tt.salary = tt.salary - 200
WHERE name = '张三'; 
                                                -- 设置Session隔离级别为READ UNCOMMITTED
                                                SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
                                                -- 张三开启事务 
                                                START TRANSACTION;        
                                                -- 1. 张三查看账户余额
                                                SELECT * FROM test_table tt;
                                                |id|name|salary|
                                                |--+----+------+
                                                | 1|张三|1800  |    

-- 3. 自动扣款发生异常,回滚事务
ROLLBACK;    

|id|name|salary|
|--+----+------+
| 1|张三  |  2000|
  1. 张三查询的账户余额并不是真实余额,发生了脏读
解决
  1. 将 MYSQL 默认的隔离级别设置为 READ COMMITED
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
  1. 执行同样的操作
START TRANSACTION;
SELECT * FROM test_table tt;
COMMIT;

// output
|id|name|salary|
|--+----+------+
| 1|张三  |  2000|

解决不可重复读

模拟场景
-- 设置Session隔离级别为 READ COMMITTED
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 1. 张三开启事务
START TRANSACTION;   
-- 2. 张三查看余额   
SELECT * FROM test_table tt;
|id|name|salary|
|--+----+------+
| 1|张三  |  2000|

                                                            -- 3. bilibli开启事务                           
                                                            START TRANSACTION;                       
                                                            -- 4. bilibli自动扣款
                                                            UPDATE test_table tt
                                                            SET tt.salary = tt.salary - 200
                                                            WHERE name = '张三'; 
                                                            -- 5. 提交事务
                                                            COMMIT;
-- 6. 张三再次查看余额                                        
SELECT * FROM test_table tt;
|id|name|salary|
|--+----+------+
| 1|张三  |  1800|

-- 7. 提交事务
COMMIT;
  1. 张三两次查询的账户余额不同,发生了不可重复读
解决
  1. 将 MYSQL 默认的隔离级别设置为 REPEATABLE READ
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
  1. 执行同样的操作
-- 1. 张三开启事务
START TRANSACTION;   
-- 2. 张三查看余额   
SELECT * FROM test_table tt;
|id|name|salary|
|--+----+------+
| 1|张三  |  2000|

                                                            -- 3. bilibli开启事务                           
                                                            START TRANSACTION;                       
                                                            -- 4. bilibli自动扣款
                                                            UPDATE test_table tt
                                                            SET tt.salary = tt.salary - 200
                                                            WHERE name = '张三'; 
                                                            -- 5. 提交事务
                                                            COMMIT;
-- 6. 张三再次查看余额                                        
SELECT * FROM test_table tt;
|id|name|salary|
|--+----+------+
| 1|张三  |  2000|

-- 7. 提交事务
COMMIT;

幻读

模拟场景
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 1.  bilibli 管理员开启事务
START TRANSACTION;   
-- 2. bilibli 管理员查看账号信息
SELECT * FROM test_table tt;
|id|name|salary|
|--+----+------+
| 1|张三  |  2000|
                                                            SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
                                                            -- 3.Boss 开启事务                           
                                                            START TRANSACTION;                       
                                                            -- 4. Boss 注册bilibli账号
                                                            INSERT INTO test_table (id,name,salary)
                                                            VALUES(2,'Boss',100000);
                                                            -- 5. 提交事务
                                                            COMMIT;
-- 6. bilibli 管理员新增 Boss 账号                                    
INSERT INTO test_table (id,name,salary)
VALUES(2,'Boss',100000);

// 发生报错,主键冲突,新增失败
// SQL 错误 [1062] [23000]: Duplicate entry '2' for key 'test_table.PRIMARY'

-- 7. bilibli 管理员再次查看账号信息,还是没有
SELECT * FROM test_table tt;
|id|name|salary|
|--+----+------+
| 1|张三  |  2000|
  1. bilibli 管理员新增失败后,再次查看,还是没有 Boss 的账号。
解决
  1. 将 MYSQL 默认的隔离级别设置为 SERIALIZABLE
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;
  1. 再次执行
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 1.  bilibli 管理员开启事务
START TRANSACTION;   
-- 2. bilibli 管理员查看账号信息
SELECT * FROM test_table tt;
|id|name|salary|
|--+----+------+
| 1|张三  |  2000|
                                                            SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
                                                            -- 3.Boss 开启事务                           
                                                            START TRANSACTION;                       
                                                            -- 4. Boss 注册bilibli账号
                                                            INSERT INTO test_table (id,name,salary)
                                                            VALUES(2,'Boss',100000);

                                                            // 发生阻塞,无法新增数据。。。。。。。。。。
                                                      
-- 5. bilibli 管理员新增 Boss 账号                                    
INSERT INTO test_table (id,name,salary)
VALUES(2,'Boss',100000);

-- 6. bilibli 管理员再次查看账号
SELECT * FROM test_table tt;
|id|name|salary|
|--+----+------+
| 1|张三  |  1800|
| 2|Boss|100000|

                                                            // 结束阻塞,新增报错 

Reference

  1. 黑马程序员 MySQL数据库入门到精通,从mysql安装到mysql高级、mysql优化全囊括
  • 13
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值