1. 事务简介
DBMS:Database Management System,数据库管理系统
事务(Transaction)是数据库管理系统(DBMS)中一个不可分割的工作单位,它由一系列操作组成,这些操作要么全部成功执行,要么全部失败回滚,不会处于中间状态
事务的主要目的是保证数据的一致性和完整性,常见的事务案例就是银行转账
我们先来看一下正常的银行转账业务
我们再来看一下转账异常的情况,如果张三取出钱后,再转账给李四的过程中业务出现了异常,会怎么样呢
如果业务出现了异常,张三的钱扣了,但是李四却没收到钱,出现了数据不一致的情况
那怎么解决呢,我们只需要把整个流程都放在一个事务里面,当所有操作都执行完成了之后,再提交事务
MySQL 的事务默认是自动提交的,也就是说,当执行一条 DML 语句时,MySQL 会立即隐式地提交事务
2. 事务操作
我们先准备一张名为 account 的表,表的结构和表数据如下
create table account
(
id int primary key AUTO_INCREMENT comment 'ID',
name varchar(10) comment '姓名',
money double(10, 2) comment '余额'
) comment '账户表';
insert into account(name, money)
VALUES ('张三', 2000),
('李四', 2000);
2.1 未控制事务
我们先来测试正常情况
-- 1.查询张三余额
select *
from account
where name = '张三';
-- 2.张三的余额减少1000
update account
set money = money - 1000
where name = '张三';
-- 3.李四的余额增加1000
update account
set money = money + 1000
where name = '李四';
测试完毕之后检查数据的状态, 可以看到数据操作前后是一致的
测试异常情况
我们先把数据都恢复到2000, 接着一次性执行以下 SQL 语句
-- 1.查询张三余额
select *
from account
where name = '张三';
-- 2.张三的余额减少1000
update account
set money = money - 1000
where name = '张三';
出错了....
-- 3.李四的余额增加1000
update account
set money = money + 1000
where name = '李四';
由于 出错了.... 这句话
不符合 SQL 语法,执行后会报错
检查最终的数据情况,发现数据在操作前后并不一致
2.2 控制事务
2.2.1 查看事务的提交方式
SELECT @@autocommit;
2.2.2 设置事务的提交方式
autocommit = 1 表示自动提交
SET @@autocommit = 0;
2.2.3 提交事务
commit;
2.2.4 回滚事务
rollback;
2.2.5 开启事务
start transaction;
2.2.6 完善转账案例
-- 开启事务
start transaction;
-- 1.查询张三余额
select *
from account
where name = '张三';
-- 2.张三的余额减少1000
update account
set money = money - 1000
where name = '张三';
-- 3.李四的余额增加1000
update account
set money = money + 1000
where name = '李四';
-- 如果正常执行完毕, 则提交事务
commit;
-- 如果执行过程中报错, 则回滚事务
rollback;
3. 事务的四大特性(ACID)
- 原子性(Atomicity): 原子性确保事务中的所有操作要么全部完成,要么全部不完成,不会出现部分完成的情况。事务中任何一个操作失败,整个事务将被回滚,就像事务从未执行过一样
- 一致性(Consistency): 一致性确保事务执行的结果是数据库状态的合法状态,即数据库在事务开始和结束时的数据满足预定义的约束条件(如外键约束、唯一性约束等)
- 隔离性(Isolation): 隔离性确保并发执行的事务彼此隔离,一个事务的中间状态不会被其他事务所见。这意味着即使在多个事务同时执行时,每个事务都感觉自己是唯一在执行的事务
- 持久性(Durability): 持久性确保一旦事务提交,其结果就永久保存在数据库中。即使发生系统故障,如电源故障或系统崩溃,已提交事务的结果也不会丢失,持久性一般是通过将事务的输出写入到持久存储设备(如硬盘)来保证
4. 并发事务引发的问题
- 赃读:一个事务读到另外一个事务还没有提交的数据
- 不可重复读:一个事务先后读取同一条记录,但两次读取的数据不同,称之为不可重复读
- 幻读:一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了"幻影"
脏读的示例(事务 A 中没提交的数据被事务 B 读取)
不可重复读的示例(事务 A 在第一步和第三步查询的 id 为 1 的数据不一致)
幻读的示例
- 事务 A 在第一步查询到 id 为 1 的数据不存在
- 事务 A 在执行 insert 语句前,事务 B 提交了事务,往表中插入了 id 为 1 的数据
- 事务 A 在第二步执行 insert 语句,执行失败,因为破坏了主键的唯一性
- 事务 A 在第三步查询到 id 为 1 的数据仍然不存在
5. 事务隔离级别
为了解决并发事务所引发的问题,在数据库中引入了事务隔离级别,事务隔离级别主要有以下几种:
5.1 演示
我们来演示一下,在不同的隔离级别情况下,并发事务可能引发的问题
5.1.1 Read Uncommitted(RU)
脏读(一个事务读到了另一个事务还没有提交的数据)
5.1.2 Read Committed(RC)
Read Committed 隔离级别虽然解决了脏读问题,但是没有解决不可重复读的问题(一个事务先后读取同一条记录,但两次读取的数据不同)
5.1.3 Repeatable Read(RR)
Read Committed 隔离级别虽然解决了不可重复读问题,但是没有解决幻读的问题(一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了"幻影")
5.1.4 Serializable
Read Committed 隔离级别解决了所有并发事务引发的问题
5.2 查看隔离级别
查看事务隔离级别
SELECT @@TRANSACTION_ISOLATION;
5.3 设置事务隔离级别
SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL { READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE }
- SESSION 设置的是当前会话(也就是当前连接)的隔离级别
- GLOBAL 设置的是全局隔离级别
事务隔离级别越高,数据越安全,但是性能越低
示例
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
5.4 面试题(为什么阿里巴巴会将 MySQL 的事务隔离级别设置为 Read Committed)
既然 MySQL 默认的事务隔离级别是 Repeatable Read,那为什么阿里巴巴会将 MySQL 的事务隔离级别设置为 Read Committed,谈谈你的理解
参考回答:
MySQL 的默认事务隔离级别是REPEATABLE READ
,这是一个相对较高的隔离级别,可以防止不可重复读和脏读
阿里巴巴将 MySQL 的事务隔离级别设置为READ COMMITTED
,可能是因为以下几个原因:
- 性能考虑:
READ COMMITTED
是比REPEATABLE READ
低的隔离级别,因此在某些情况下可能会提供更好的性能。特别是在高并发环境下,READ COMMITTED
可以减少锁的持有时间,从而提高系统的响应速度和吞吐量 - 业务场景:对于阿里巴巴的业务场景来说,幻读可能不是一个大问题,
READ COMMITTED
足以满足需求,而REPEATABLE READ
可能会造成一些不必要的性能开销 - 可控的并发问题:阿里巴巴可能有能力管理和控制其系统中的并发问题,即使使用
READ COMMITTED
。这意味着,阿里巴巴可能可以通过其他手段(如调整应用层逻辑、优化数据库设计和使用合适的索引等)来减少并发问题带来的影响 - 避免死锁:降低事务的隔离级别可以减少发生死锁的概率,特别是在频繁进行更新操作的环境中
阿里巴巴将 MySQL 的事务隔离级别设置为READ COMMITTED
,可能是基于性能、并发控制、业务需求和故障处理等多方面的考虑,每个公司和组织都应该根据自己的业务场景来决定使用哪个隔离级别