MySQL的事务(Transaction)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务能把数据库从一个一致性状态转变为另一个一致性状态。
1 事务得特性
A/Atomicity
:原子性C/Consistency
:一致性I/Isolation
:独立性/隔离性D/Durability
:持久性
1.1 原子性
开启事务中得命令要不全部执行成功,要不全部执行失败,事务中得操作可以看作是一个原子操作不能分割。例如A给B转账,A-100 B+100,要不全部成功了要不就全部失败了,不存在但单独A成功或者B单独成功。
1.2 一致性
一致性表明事务发生得前后,数据是一致得。比如A给B转账,转账前后得A+B得总金额都是保持不变得,虽然转账导致了A和B得金额发生了变化,但是总得金额在前后保持一致性。
1.3 隔离性
两个事务之间的是相互隔离的,相互不干扰。MySQL中存在四种隔离等级
-
读未提交:一个事务能读到另外事务未提交的数据
-
读已提交:一个事务可以读到另一个事务已经提交的数据
-
可重复读:在该事务中每次读取的数据都是一致的
-
序列化/串行化:串行执行MySQL的命令。
1.4 持久性
持久性表示MySQL的数据会写入到磁盘做持久化处理。
2 Mysql中的事务操作
MySQL默认自动开启事务的,默认状态下一条语句就是一个事务,更改的Sql语句执行完成之后事务默认是自动提交的。
-- 查询是否开启自动提交
show variables like 'autocommit';
而且事务是基于连接的,创建了一个MySQL的连接会从MySQL连接池中分配一个线程,而开启的事务是基于本次的连接,操作不同的表数据可以加入到同一个事务中。
start transaction | begin | begin work
:开启一个事务commit
:提交一个事务rollback
:回滚一个事务
-- 创建学生表
CREATE TABLE students (
id INT auto_increment NOT NULL,
stu_no varchar(100) NULL COMMENT '学号',
name varchar(100) NULL COMMENT '学生名字',
age INT NULL COMMENT '学生年龄',
class_id INT NULL COMMENT '班级id',
CONSTRAINT students_PK PRIMARY KEY (id)
);
--插入学生数据
INSERT INTO students (stu_no, name, age, class_id) VALUES
('1', '小红', 10, 1),
('2', '小蓝', 11, 2),
('3', '小黑', 13, 3),
('4', '小黑', 14, 1);
--示例1 rollback回滚事务
--窗口开启事务1
start transaction;
update students set age=100 where name="小红";
--窗口开启事务2
start transaction;
select * from students;
--事务1回滚
rollback;
--commit提交事务
start transaction;
update students set age=100 where name="小红";
commit;
--设置事务的回滚点
start transaction;
update students set age=100 where name="小黑";
savepoint update1;
update students set age=100 where name="小红";
savepoint update2;
update students set age=100 where name="不存在数据";
rollback to update1;
commit;
3 事务中发生的问题
3.1 脏写
AB同时开启事务,在同时刻同时对一个数据进行写操作。可能存在
- A更改成功了,但是B失败进行回退的时候,把A修改的数据还原了
- A更改成功,B也更改成功,但是B提交,A在提交的时候把B的数据覆盖了。
上面的两种情况都会出现MySQL数据的脏写问题。
3.2 脏读
一个事务2在执行过程读取到事务1未提交的数据,就是脏读,如果事务1出现异常回退,数据1会还原,而事务2获取到的数据就时脏数据。
3.3 不可重复读
如果事务2在第一次读取数据时事务还未提交,第二次读取时事务1提交了,会产生两个读取的结果不同,也就是说事务2读取事务1的已提交的内容,导致不可重复读的问题。
3.4 幻读
事务2在操作一堆数据的时候,事务1插入了一条数据,提交,明明没有数据为说存在数据,像是幻觉,这就是幻读。
4 MySQL的隔离级别
4.1 数据准备
-- 创建学生表
CREATE TABLE users (
id INT auto_increment NOT NULL,
user_no varchar(100) NULL COMMENT '用户编号',
name varchar(100) NULL COMMENT '用户名字',
age INT NULL COMMENT '用户年龄',
CONSTRAINT students_PK PRIMARY KEY (id)
);
--插入学生数据
INSERT INTO users (user_no, name, age) VALUES
('1', '小红', 10),
('2', '小蓝', 11),
('3', '小黑', 13),
('4', '小黑', 14);
4.2 读未提交
事务1 | 事务2 |
---|---|
start transaction; | start transaction; |
update users set age=age+1; | update users set age=age+1; |
commit; | commit; |
如图所示,两个事务中修改同数据的时候会加一个互斥锁,当事务1获取锁时,事务2一致等待事务1的写互斥锁,当事务1完成时才释放锁,事务2获取了写互斥锁才能将数据写入。
事务1 | 事务2 |
---|---|
SELECT @@transaction_isolation; | SELECT @@transaction_isolation; |
set global transaction isolation level read uncommitted; | set global transaction isolation level read uncommitted; |
exit | exit |
mysql -uroot -p 123456 | mysql -uroot -p 123456 |
use test; | use test; |
SELECT @@transaction_isolation; | SELECT @@transaction_isolation; |
start transaction; | |
update users set user_no=100 where name=“小红”; | |
select * from users where name=“小红”; | |
rollback; |
读未提交利用写互斥锁解决了数据库中的脏写的问题,但是依旧存在脏读、不可重复度和幻读问题
4.3 读已经提交
事务1 | 事务2 |
---|---|
set global transaction isolation level read committed; | set global transaction isolation level read committed; |
exit | exit |
mysql -uroot -p 123456 | mysql -uroot -p 123456 |
use test; | use test; |
SELECT @@transaction_isolation; | SELECT @@transaction_isolation; |
start transaction; | start transaction; |
select * from users where name=“小红”; | |
update users set age=200 where name=“小红”; | |
commit; | |
select * from users where name=“小红”; | |
commit; | |
读已经提交 通过MVCC多版本并发控制的技术解决了脏读的问题,但是依旧存在不可重复读、幻读的问题。
读已提交中的MVCC机制相当于事务第一次读取数据创建一个ReadView
(相当于快照),读取原本表中上一次提交的老数据。其他事务操作此数据的事务提交后此时MVCC
机制又会创建一个新的ReadView
,然后读取到最新的已提交的数据。
4.4 可重复读
-- 设置用户编号为唯一索引
alter table users add unique index un_no(user_no);
事务1 | 事务2 |
---|---|
set global transaction isolation level repeatable read; | set global transaction isolation level repeatable read; |
exit | exit |
mysql -uroot -p123456 | mysql -uroot -p123456 |
use test; | use test; |
SELECT @@transaction_isolation; | SELECT @@transaction_isolation; |
start transaction; | start transaction; |
select * from users where name=“小红”; | |
update users set age=300 where name=“小红”; | |
commit; | select * from users where name=“小红”; |
start transaction; | |
insert into users(user_no, name, age) values (“6”, “小青”, 10); | |
commit; | select * from users; |
insert into users(user_no, name, age) values (“5”, “小黄”, 12); |
可重复读 解决了不可重复读的问题,但是依旧存在幻读的现象。
可重复读依旧是利用mvcc来解决不可重复读的问题,在读已提交中,一个事务中每次查询数据时,都会创建一个新的ReadView
,然后读取最近已提交的事务数据,因此就会造成不可重复读的问题。而在可重复读级别中,则不会每次查询时都创建新的ReadView
,而是在一个事务中,只有第一次执行查询会创建一个ReadView
在这个事务的生命周期内,所有的查询都会从这一个ReadView
中读取数据,从而确保了一个事务中多次读取相同数据是一致的,也就是解决了不可重复读问题。
innoDB的默认隔离级别是可重复读
4.5 串行化
事务1 | 事务2 |
---|---|
set global transaction isolation level serializable; | set global transaction isolation level serializable; |
exit | |
mysql -uroot -p123456 | |
use test; | |
SELECT @@transaction_isolation; | SELECT @@transaction_isolation; |
start transaction; | start transaction; |
select * from users; | |
insert into users(user_no, name, age) values (“6”, “小青”, 10); |
这个隔离级别是最高的级别,处于该隔离级别的MySQL
绝不会产生任何问题,因为从它的名字上就可以得知:序列化意思是将所有的事务按序排队后串行化处理,也就是操作同一张表的事务只能一个一个执行,事务在执行前需要先获取表级别的锁资源,拿到锁资源的事务才能执行,其余事务则陷入阻塞,等待当前事务释放锁。