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;

image-20240506164530057

默认是自动提交的,使用下方命令查看。

SELECT @@autocommit;

image-20240506165029121

关闭自动提交,方可显式使用命令。

COMMIT; 提交事务

ROLLBACK;回滚事务

SET AUTOCOMMIT = 0;
SELECT @@autocommit;

image-20240506165056303


在 MySQL 中,每个连接都是独立的,因此在一个窗口中关闭了自动提交,在新开的窗口中并不会生效。

要在新窗口中也应用关闭自动提交的设置,你需要在新窗口中执行相同的 SQL 语句 SET AUTOCOMMIT = 0

image-20240506165712783


后方我们站在左侧窗口1(当前事务)的角度来看数据,假设窗口2(其他事务)的操作我们是不知情的。

然后我们就会在窗口1中查询到各种奇奇怪怪的数据。

image-20240506170032397

2.1.1 脏读

2.1.1.1 现象

脏读:读取到了其它事务中未提交的数据。

2.1.1.2 演示

窗口2将yang的年龄修改为20,尚未提交,在窗口1中查询,查到的数据是窗口2中这个尚未提交的数据20。

窗口1读取到了窗口2中没有提交的数据20。

image-20240506171808208

窗口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。

image-20240506173126761

又查了下,年龄咋变成30了。

image-20240506173201067

窗口1查询,每次查出来的结果还不一样了,太不靠谱了。

2.1.3 幻读

2.1.3.1 定义

幻读是指一次事务中前后数据量发生变化

区分不可重复读,不可重复读是在单条或者多条数据的角度来看待数据,这种可以通过行锁之类的操作来解决。

幻读是在表整体角度来看待数据,这个时候整体数据量发生变化,仅仅通过行锁已经无法解决了。

2.1.3.2 演示

窗口2中先插入了一条数据,此时在窗口1中查询总条数为3(由2增加到3)。

然后窗口2中又再次插入了一条数据,窗口1查询总条数为4。

窗口1就很奇怪,这个数据量咋变来变去。

首次查询条数,是3。

image-20240506174204160

再次查询,条数又变成了4。

image-20240506174234298

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;

image-20240506160753174

posted @ 2024-05-07 14:04  羊37  阅读(19)  评论(0编辑  收藏  举报