事务
2020-11-30 本文已影响0人
爱折腾的傻小子
原子性(Atomicity)
- 事务的整个过程如原子操作一样,最终要么全部成功,或者全部失败,这个原子性是从最终结果来看的,从最终结果来看这个过程是不可分割的。
一致性(Consistency)
- 一个事务必须使数据库从一个一致性状态变换到另一个一致性状态
隔离性(Isoladon)
- 一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(Durability)
- 一个事务一旦提交,他对数据库中数据的改变就应该是永久性的。当事务提交之后,数据会持久化到硬盘,修改是永久性的。
Mysql中事务操作
- mysql中事务默认是隐式事务,执行insert、update、delete操作的时候,数据库自动开启事务、提交或回滚事务。
- 是否开启隐式事务是由变量 autocommit 控制的
隐式事务
show variables like 'autocommit';
```sql
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set, 1 warning (0.00 sec)
-- autocommit 为ON表示开启了自动提交。
显示事务
- 需要手动开启、提交或回滚,由开发者自己控制。
- set autocommit=0; 设置不自动提交事务
- commit|rollback; 执行事务操作
set autocommit=0; -- 开启事务
insert into test1 values(1); -- 插入数据
commit; -- 提交
set autocommit=0; -- 开启事务
insert into test1 values(2); -- 插入数据
rollback; -- 回滚
- start transaction; 开启事务
- commit|rollback; 执行事务操作
start transaction; -- 开启事务
insert into test1 values (2); -- 插入数据
insert into test1 values (3); -- 插入数据
commit; -- 提交
start transaction; -- 开始事务
delete from test1; -- 删除表
rollback; -- 回滚
savepoint关键字
- 我们可以将一大批操作分为几个部分,然后指定回滚某个部分。可以使硬 savepoin 来实现
- savepoint 需要结合 rollback to sp1 一起使用,可以将保存点 sp1 到
rollback to 之间的操作回滚掉
start transaction; -- 开启事务
insert into test1 values (1); -- 插入一条数据
savepoint part1; -- 设置⼀个保存点
insert into test1 values (2);
-- 将savepint = part1的语句到当前语句之间所有的操作回滚
rollback to part1;
commit; -- 提交
只读事务
- 表示在事务中执行的是一些只读操作,如查询,但是不会做insert、update、delete操作,数据库内部对只读事务可能会有一些性能上的优化
- start transaction read only;
start transaction read only; -- 开启只读事务
select * from test1;
delete from test1; -- 只读事务中执⾏delete会报错
commit;
脏读
- 一个事务在执行的过程中读取到了其他事务还没有提交的数据
读已提交
- 一个事务操作过程中可以读取到其他事务已经提交的数据
可重复读
- 一个事务操作中对于一个读取操作不管多少次,读取到的结果都是一样的
幻读
- 幻读在可重复读的模式下才会出现,其他隔离级别中不会出现
事务的隔离级别
- 事务隔离级别主要是解决了上面多个事务之间数据可见性及数据正确性的问题
- 隔离级别分为4种:
- 读未提交:READ-UNCOMMITTED
- 读已提交:READ-COMMITTED
- 可重复读:REPEATABLE-READ
- 串⾏:SERIALIZABLE
- 上面4中隔离级别越来越强,会导致数据库的并发性也越来越低
- 查看隔离级别
show variables like 'transaction_isolation';
/*
+-----------------------+----------------+
| Variable_name | Value |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+----------------+
1 row in set, 1 warning (0.00 sec)
*/
隔离级别的设置
- 修改mysql中的my.init文件,我们将隔离级别设置为:READ-UNCOMMITTED
- transaction-isolation=READ-UNCOMMITTED
READ-UNCOMMITTED:读未提交
- transaction-isolation=READ-UNCOMMITTED
show variables like 'transaction_isolation';
/*
+-----------------------+----------------+
| Variable_name | Value |
+-----------------------+----------------+
| transaction_isolation | READ-UNCOMMITTED |
+-----------------------+----------------+
1 row in set, 1 warning (0.00 sec)
*/
- 读未提交情况下,可以读取到其他事务还未提交的数据,多次读取结果不一样,出现了脏读、不可重复读
-- A窗口如下:
start transaction; -- 开启事务
select * from test1; -- 查询
/*
Empty set (0.00 sec)
*/
select * from test1;
/*
+------+
| a |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
*/
commit;
/*
Query OK, 0 rows affected (0.00 sec)
*/
-- B窗口如下:
start transaction; -- 开启事务
insert into test1 values (1); -- 插入
select * from test1;
/*
+------+
| a |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
*/
commit;
/*
Query OK, 0 rows affected (0.00 sec)
*/
READ-COMMITTED:读已提交
- transaction-isolation=READ-COMMITTED
show variables like 'transaction_isolation';
/*
+-----------------------+----------------+
| Variable_name | Value |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+----------------+
1 row in set, 1 warning (0.00 sec)
*/
- 读已提交情况下,无法读取到其他事务还未提交的数据,可以读取到其他事务已经提交的数据,多次读取结果不一样,未出现脏读,出现了读已提交、不可重复读。
-- A窗口如下:
start transaction; -- 开启事务
select * from test1;
/*
Empty set (0.00 sec)
*/
select * from test1;
/*
Empty set (0.00 sec)
*/
select * from test1;
/*
+------+
| a |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
*/
commit;
-- B窗口如下:
start transaction; -- 开启事务
insert into test1 values (1);
/*
Query OK, 1 row affected (0.00 sec)
*/
select * from test1;
/*
+------+
| a |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
*/
commit;
/*
Query OK, 0 rows affected (0.00 sec)
*/
REPEATABLE-READ:可重复读
- transaction-isolation=REPEATABLE-READ
show variables like 'transaction_isolation';
/*
+-----------------------+----------------+
| Variable_name | Value |
+-----------------------+----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+----------------+
1 row in set, 1 warning (0.00 sec)
*/
- 可重复读情况下,未出现脏读,未读取到其他事务已提交的数据,多次读取结果一致,即可重复读
-- A窗口如下:
start transaction; -- 开启事务
select * from test1;
/*
Empty set (0.00 sec)
*/
select * from test1;
/*
Empty set (0.00 sec)
*/
commit;
/*
Query OK, 0 rows affected (0.00 sec)
*/
select * from test1;
/*
+------+
| a |
+------+
| 1 |
| 1 |
+------+
2 rows in set (0.00 sec)
*/
-- B窗口如下:
start transaction; -- 开启事务
insert into test1 values (1);
/*
Query OK, 1 row affected (0.00 sec)
*/
select * from test1;
/*
+------+
| a |
+------+
| 1 |
| 1 |
+------+
2 rows in set (0.00 sec)
*/
commit;
/*
Query OK, 0 rows affected (0.00 sec)
*/
幻读演示
- 幻读只会在 REPEATABLE-READ (可重复读)级别下出现,需要先把隔离级别改为可重复读
- transaction-isolation=REPEATABLE-READ
show variables like 'transaction_isolation';
/*
+-----------------------+----------------+
| Variable_name | Value |
+-----------------------+----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+----------------+
1 row in set, 1 warning (0.00 sec)
*/
-- 数据准备
create table t_user(
id int primary key,
name varchar(16)
unique name
);
insert into t_user values
(1,'路人甲Java'),
(2,'路人甲Java');
/*
ERROR 1062 (23000): Duplicate entry '路人甲Java' for key 'name'
*/
select * from t_user;
/*
Empty set (0.00 sec)
*/
-- 上面我们创建t_user表,name添加了唯一约束,表示name不能重复,否则报错
-- A窗口如下:
start transaction;
/*
Query OK, 0 rows affected (0.00 sec)
*/
select * from t_user where name='路人甲Java';
/*
Empty set (0.00 sec)
*/
insert into t_user values (2,'路人甲Java');
/*
ERROR 1062 (23000): Duplicate entry '路人甲Java' for key 'name'
*/
commit;
/*
Query OK, 0 rows affected (0.00 sec)
*/
-- B窗口如下:
start transaction;
insert into t_user values (1,'路⼈甲Java');
/*
Query OK, 1 row affected (0.00 sec)
*/
select * from t_user;
/*
+----+---------------+
| id | name |
+----+---------------+
| 1 | 路⼈甲Java |
+----+---------------+
1 row in set (0.00 sec)
*/
commit;
/*
Query OK, 0 rows affected (0.00 sec)
*/
A想插入数据 路人甲Java ,插入之前先查询了一下(T5时刻)该用户是否存在,发现不存在,然后在T7时刻执行插入,报错了,报数据已经存在了,因为T6时刻 B 已经插入了 路人甲Java 。
然后A有点郁闷,刚才查的时候不存在的,然后A不相信自己的眼睛,又去查一次(T8时刻),发现 路人甲Java 还是不存在的。
数据明明不存在啊,为什么无法插入呢?这不是懵逼了么,A觉得如同发生了幻觉一样
SERIALIZABLE:串⾏
- SERIALIZABLE会让并发的事务串行执行(多个事务之间读写、写读、写写会产生互斥,效果就是串行执行,多个事务之间的读读不会产生互斥)
- 读写互斥:事务A中先读取操作,事务B发起写入操作,事务A中的读取会导致事务B中的写入处于等待状态,直到A事务完成为止
- 表示我开启一个事务,为了保证事务中不会出现上面说的问题(脏读、不可重复读、读已提交、幻读),那么我读取的时候,其他事务有修改数据的操作需要排队等待,等待我读取完成之后,他们才可以继续
- 写读、写写也是互斥的,读写互斥类似
- transaction-isolation=SERIALIZABLE
show variables like 'transaction_isolation';
/*
+-----------------------+--------------+
| Variable_name | Value |
+-----------------------+--------------+
| transaction_isolation | SERIALIZABLE |
+-----------------------+--------------+
1 row in set, 1 warning (0.00 sec)
*/
-- 事务只能串行执行了。串行情况下不存在脏读、不可重复读、幻读的问题了
关于隔离级别的选择
- 需要对各种隔离级别产生的现象非常了解,然后选择的时候才能游刃有余
- 隔离级别越高,并发性也低,比如最高级别 SERIALIZABLE 会让事物串行执行,并发操作变成串行了,会导致系统性能直接降低
- 具体选择哪种需要结合具体的业务来选择
- 读已提交(READ-COMMITTED)通常用的比较多
总结
- 理解事务的4个特性:原子性、一致性、隔离性、持久性
- 掌握事务操作常见命令的介绍
- set autocommit 可以设置是否开启⾃动提交事务
- start transaction:开启事务
- start transaction read only:开启只读事物
- commit:提交事务
- rollback:回滚事务
- savepoint:设置保存点
- rollback to 保存点:可以回滚到某个保存点
- 掌握4种隔离级别及了解其特点
- 了解脏读、不可重复读、幻读