Mysql-事务的基本特性和隔离级别
0.背景
在数据库中,事务是一组数据库操作,可以将事务操作视为一个基本的工作单元。
1.事务的基本特性
事务的基本特性“ACID”
对于事务呢,就是这一组sql操作,要确保ACID这4个基本特性。
哎,八股文不好背,我记忆方式是:一元吃个(原持隔)
- 原子性(Atomicity):事务中的所有操作要么全部执行成功,要么全部失败回滚。如果一个操作失败,整个事务都会被回滚到初始状态,不会留下部分完成的结果。
- 一致性(Consistency):事务执行前后,数据库的完整性约束保持一致。换句话说,事务执行后,数据库从一个一致的状态转换到另一个一致的状态。
- 隔离性(Isolation):事务的执行结果对其他事务是隔离的,一个事务的执行不应该被其他事务所影响。事务之间应该相互独立,即使在并发执行的情况下也是如此。
- 持久性(Durability):一旦事务提交成功,其所做的修改将永久保存在数据库中,并且不会因为系统故障而丢失。即使系统崩溃,数据库也能够在恢复后保持原有的状态。
2.事务的隔离级别
当涉及到多个事务并行执行时,就可能会涉及到多个事务操作相同的数据,这个时候就会出现一些问题。
2.1 可能的问题
数据库并发场景:
读-读
:不存在任何问题,也不需要并发控制读-写
:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读写-写
:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失
后方描述读-写
这个场景下的几个问题。
方便后方演示,建个表准备下。
-- 创建1张u_user表
CREATE TABLE `u_user` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(10) NOT NULL COMMENT '姓名',
`age` int NOT NULL COMMENT '年龄',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
);
-- 插入2条数据
INSERT INTO `u_user` (`name`, `age`) VALUES ('yang', 12);
INSERT INTO `u_user` (`name`, `age`) VALUES ('su', 13);
-- 查询下
SELECT * from u_user;
默认是自动提交的,使用下方命令查看。
SELECT @@autocommit;
关闭自动提交,方可显式使用命令。
COMMIT;
提交事务
ROLLBACK;
回滚事务
SET AUTOCOMMIT = 0;
SELECT @@autocommit;
在 MySQL 中,每个连接都是独立的,因此在一个窗口中关闭了自动提交,在新开的窗口中并不会生效。
要在新窗口中也应用关闭自动提交的设置,你需要在新窗口中执行相同的 SQL 语句
SET AUTOCOMMIT = 0
。
后方我们站在左侧窗口1(当前事务)的角度来看数据,假设窗口2(其他事务)的操作我们是不知情的。
然后我们就会在窗口1中查询到各种奇奇怪怪的数据。
2.1.1 脏读
2.1.1.1 现象
脏读:读取到了其它事务中未提交的数据。
2.1.1.2 演示
窗口2将yang的年龄修改为20,尚未提交,在窗口1中查询,查到的数据是窗口2中这个尚未提交的数据20。
窗口1读取到了窗口2中没有提交的数据20。
窗口1拿着未提交的脏数据20继续操作,然后窗口2回滚了?宏观来看,yang这个字段就没有存在过20这个值,似乎读取到了一个不存在的数据,一切都乱了。
2.1.2 不可重复读
假设我们解决了脏读问题,即不会读取到未提交的数据,这个时候还是会有问题。
2.1.2.1 现象
不可重复读:读取到了其他事务已经提交的数据,但前后读取的结果不同。
区分脏读,不可重复读运气好点,读到的是已经提交过的数据(也许这个事务时间比较长)。
2.1.2.2 演示
窗口2中先后将yang的年龄修改为20、30,也做了提交(不是脏读数据)。
但是在窗口1中查询时,前后查询出来了20、30这两个值。
就会疑惑,咋查了2次值还不一样呢?即窗口1中多次查询年龄数据,前后查询的结果不一致。
首次查询得到年龄值是20。
又查了下,年龄咋变成30了。
窗口1查询,每次查出来的结果还不一样了,太不靠谱了。
2.1.3 幻读
2.1.3.1 定义
幻读是指一次事务中前后数据量发生变化
区分不可重复读,不可重复读是在单条或者多条数据的角度来看待数据,这种可以通过行锁之类的操作来解决。
幻读是在表整体角度来看待数据,这个时候整体数据量发生变化,仅仅通过行锁已经无法解决了。
2.1.3.2 演示
窗口2中先插入了一条数据,此时在窗口1中查询总条数为3(由2增加到3)。
然后窗口2中又再次插入了一条数据,窗口1查询总条数为4。
窗口1就很奇怪,这个数据量咋变来变去。
首次查询条数,是3。
再次查询,条数又变成了4。
2.2 事务隔离级别
通过设置事务的隔离级别,来解决对应的问题。
隔离性从低到高,并发性从高到低。
嗯,为了保持更强的隔离性,需要付出的措施就更多,并发能力当然有所降低咯。
隔离级别 | 脏读(是否存在) | 不可重复读(是否存在) | 幻读(是否存在) |
---|---|---|---|
RU Read uncommitted(读未提交) | √ | √ | √ |
RC Read committed(读已提交) | × | √ | √ |
RR Repeatable read(可重复读)默认的隔离级别 | × | × | √ |
Serializable(可串行化) | × | × | × |
2.2.1 Read uncommitted(RU读未提交)
该隔离级别的事务可以看到其他事务中未提交的数据。
该隔离级别因为可以读取到其他事务中未提交的数据,而未提交的数据可能会发生回滚,因此我们把该级别读取到的数据称之为脏数据,把这个问题称之为脏读。
-- 将当前会话(session)的事务隔离级别设置为读未提交。
-- 这个设置仅对当前会话有效,对其他会话不会产生影响。
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
2.2.2 Read committed(RC读已提交)
该隔离级别的事务能读取到已经提交事务的数据,因此它不会有脏读问题。
但由于在事务的执行中可以读取到其他事务提交的结果,所以在不同时间的相同 SQL 查询中,可能会得到不同的结果,这种现象叫做不可重复读。
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
2.2.3 Repeatable read(RR可重复读)
MySQL 的默认事务隔离级别,它能确保同一事务多次查询的结果一致。
但也会有新的问题,比如此级别的事务正在执行时,另一个事务成功的插入了某条数据,但因为它每次查询的结果都是一样的,所以会导致查询不到这条数据,自己重复插入时又失败(因为唯一约束的原因)。
明明在事务中查询不到这条信息,但自己就是插入不进去,这就叫幻读 (Phantom Read)。
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
2.2.4 Serializable(可串行化)
事务最高隔离级别,它会强制事务排序,使之不会发生冲突,从而解决了脏读、不可重复读和幻读问题,但因为执行效率低,所以真正使用的场景并不多。
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
2.3 扩展
2.3.1 查询事务的隔离级别
select @@transaction_isolation;