MySQL最为最流行的开源数据库,其重要性不言而喻,也是大多数程序员接触的第一款数据库,深入认识和理解MySQL也比较重要。
本篇博客阐述MySQL的事务的定义和特性,原子性,一致性,隔离性,持久性;然后结合实际的案例详细分析了事务的隔离级别:序列化(SERIALIZABLE)、可重复读(REPEATABLE READ)、提交读(READ COMMITTED)、未提交读(READ UNCOMMITTED)。
本系列文章合集如下:
【合集】MySQL的入门进阶强化——从 普通人 到 超级赛亚人 的 华丽转身
1.事务(TRANSACTION)是一个不可分割的逻辑单元,包含了一组数据库操作命令,并且把所有的命令作为一个整体向系统提交,要么都执行、要么都不执行;
2.隔离级别和脏读、不可重复读以及幻象读的对应关系如下:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED | 允许 | 允许 | 允许 |
READ COMMITED | 不允许 | 允许 | 允许 |
REPEATABLE READ 【默认的隔离级别】 | 不允许 | 不允许 | 允许 |
SERIALIZABLE | 不允许 | 不允许 | 不允许 |
3.在MySQL数据库中,默认的事务隔离级别是REPEATABLE READ 可重复读;
事务(TRANSACTION)是一个不可分割的逻辑单元,包含了一组数据库操作命令,并且把所有的命令作为一个整体向系统提交,要么都执行、要么都不执行。
事务必须具备以下四个属性,简称ACID 属性
1、原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败。
比如在同一个事务中的SQL语句,要么全部执行成功,要么全部执行失败。
2、一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
换一种方式理解就是:事务按照预期生效,数据的状态是预期的状态。
比如账户A和账户B两者的余额加起来总共是5000,那么不管A和B之间如何转账,转账几次,事务结束后两个账户的钱相加起来最终还是5000。
3、隔离性(Isolation)
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间,要相互隔离。
在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同,分别是:未授权读取,授权读取,可重复读取和串行化。
4、持久性(durability)
一旦事务提交,那么数据的状态就会被永久的保存到数据库中。
即使发生系统崩溃或机器宕机等故障,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束的状态。
1、开始事务
BEGIN ;
或START TRANSACTION;
2、提交事务
COMMIT ;
3、回滚(撤销)事务
ROLLBACK ;
使用事务有两种方式,分别为显式事务
和 隐式事务
。
(1)创建account表,并插入测试数据
create table account(
id int primary key auto_increment,
name varchar(20) unique not null,
balance int
);
insert into account values(null,'zs',1000);
insert into account values(null,'lisi',1);
commit之后数据才提交
事物回滚,删除一条数据进行测试
回滚
BEGIN;
delete from account where name='zs';
ROLLBACK
保存点SAVEPOINT
在事务中创建保存点,方便后续针对保存点进行回滚。一个事务中可以存在多个保存点。
回滚到指定位置
begin;
delete from account where name='zs';
savepoint first;
delete from account where name='lisi';
ROLLBACK TO first;
COMMIT
DROP TABLE account;
create table account(
id int primary key auto_increment,
name varchar(20) unique not null,
balance int
);
insert into account values(null,'zs',1000);
insert into account values(null,'lisi',1);
(1)在MySQL中执行DML语句,会自动提交事物。
delete from account where name='zs';
(2)MySQL中有一个系统变量 autocommit
, 可以查看是否开启自动提交事物。
SHOW VARIABLES LIKE 'autocommit';
(3)把系统变量autocommit
的值设置为 OFF
,则会关闭自动提交。
SET autocommit = OFF;
#或
SET autocommit = 0;
(4)再次执行DML语句,发现不会自动提交。
insert into account values(null,'zs',1000);
(5)此时必须显示的提交事物才能生效
commit;
MySQL 中事务的隔离级别一共分为四种,分别如下:
时刻 | 事务1 | 事务2 |
---|---|---|
T0 | 读取商品初始库存为2 | |
T1 | 扣减库存,库存为1 | |
T2 | 读取商品库存为1 | |
T3 | 回滚事务,库存为2 | |
T4 | 扣减库存,提交事务 | |
T5 | 查询库存为1 |
在MySQL数据库中,默认的事务隔离级别是REPEATABLE READ 可重复读
通过如下 SQL 可以查看数据库实例默认的全局隔离级别和当前 session 的隔离级别。
(1)MySQL8 之前使用如下命令查看 MySQL 隔离级别:
SELECT @@GLOBAL.tx_isolation, @@tx_isolation;
(2)MySQL8 开始,通过如下命令查看 MySQL 默认隔离级别:
SELECT @@GLOBAL.transaction_isolation, @@transaction_isolation;
(3)通过如下命令可以修改隔离级别(建议修改当前 session 隔离级别即可,不用修改全局的隔离级别):
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
上面这条 SQL 表示将当前 session 的数据库隔离级别设置为 READ UNCOMMITTED,设置成功后,再次查询隔离级别,发现当前 session 的隔离级别已经变了。
注意,如果只是修改了当前 session 的隔离级别,则换一个 session 之后,隔离级别又会恢复到默认的隔离级别,所以我们测试时,修改当前 session 的隔离级别即可。
该事物隔离级别会导致出现脏读、不可重复读、幻读。
DROP TABLE account;
create table account(
id int primary key auto_increment,
name varchar(20) unique not null,
balance int
);
insert into account values(null,'zs',1000);
insert into account values(null,'lisi',1);
一个事务读到另外一个事务还没有提交的数据,称之为脏读。具体操作如下
会话A
START TRANSACTION;
UPDATE account set balance = balance -1 WHERE name = 'lisi'
会话B
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT @@GLOBAL.transaction_isolation, @@transaction_isolation;
SELECT * FROM account
可以看到,A 窗口中的事务,虽然还未提交,但是 B 窗口中已经可以查询到数据的相关变化了。
这就是脏读问题。
不可重复读是指一个事务先后读取同一条记录,但两次读取的数据结果不同,称之为不可重复读。
-- 会话A
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
SELECT * FROM account WHERE name ='lisi';
SELECT SLEEP(10);
SELECT * FROM account WHERE name ='lisi';
COMMIT;
-- 会话B
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
UPDATE account SET balance = balance -500 WHERE name ='lisi';
COMMIT;
lisi的账户已经发生了变化,即前后两次查看 lisi账户,结果不一致,这就是不可重复读
和脏读的区别在于,脏读是看到了其他事务未提交的数据,而不可重复读是看到了其他事务已经提交的数据
(由于当前 SQL 也是在事务中,因此有可能并不想看到其他事务已经提交的数据)。
是指当事务不是独立执行时发生的一种现象。
幻象读和不可重复读非常像,看名字就是产生幻觉了。
-- 会话A
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
SELECT * FROM account;
SELECT SLEEP(10);
SELECT * FROM account;
COMMIT;
-- 会话B
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
insert into account values(null,'wangwu',1);
COMMIT;
幻读。
幻读
说的是存不存在的问题:原来不存在的,现在存在了,则是幻读不可重复读
说的是变没变化的问题:原来是A,现在却变为了B,则为不可重复读和 READ UNCOMMITTED 相比,READ COMMITTED 主要解决了脏读的问题,对于不可重复读和幻象读则未解决。
将事务的隔离级别改为 READ COMMITTED
之后,重复上面关于脏读案例的测试,发现已经不存在脏读问题了;重复上面关于不可重复读案例的测试,发现不可重复读问题依然存在。
-- 会话A
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
UPDATE account set balance = balance -1 WHERE name = 'lisi'
-- 会话B
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT @@GLOBAL.transaction_isolation, @@transaction_isolation;
SELECT * FROM account
和 READ COMMITTED 相比,REPEATABLE READ 进一步解决了不可重复读的问题,但是幻象读则未解决。
-- 会话A
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT * FROM account WHERE name ='lisi';
SELECT SLEEP(10);
SELECT * FROM account WHERE name ='lisi';
COMMIT;
-- 会话B
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT @@GLOBAL.transaction_isolation, @@transaction_isolation;
START TRANSACTION;
UPDATE account SET balance = balance +500 WHERE name ='lisi';
COMMIT;
注意,REPEATABLE READ 也是 InnoDB 引擎的默认数据库事务隔离级别
SERIALIZABLE 提供了事务之间最大限度的隔离,在这种隔离级别中,事务一个接一个顺序的执行,不会发生脏读、不可重复读以及幻象读问题,最安全。
如果设置当前事务隔离级别为 SERIALIZABLE,那么此时开启其他事务时,就会阻塞,必须等当前事务提交了,其他事务才能开启成功,因此前面的脏读、不可重复读以及幻象读问题这里都不会发生。
隔离级别和脏读、不可重复读以及幻象读的对应关系如下:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED | 允许 | 允许 | 允许 |
READ COMMITED | 不允许 | 允许 | 允许 |
REPEATABLE READ 【默认的隔离级别】 | 不允许 | 不允许 | 允许 |
SERIALIZABLE | 不允许 | 不允许 | 不允许 |