mysql事务
– mysql中,事务其实是一个最小的不可分割的工作单元,事务能够保证一个业务的完整性.
– 比如我们的银行转账:
1 2 3 4 5
| update user set money = money - 100 where name = 'a';
update user set money = money + 100 where name = 'b';
|
– 实际的程序中,如果只有一条语句执行成功了,而另外一条没有执行成功,则会出现数据前后不一致.
– 因此,在执行多条有关联的sql语句时,事务可能会要求这些sql语句要么同时成功,要么就同时失败.
mysql中如何控制事务?
- mysql默认是开启事务的(自动提交)
1 2 3 4 5 6
| select @@autocommit; + | @@autocommit | + | 1 | +
|
默认事务开启(自动提交)的作用:
– 当我们去执行一个sql语句的时候,效果会立即体现出来,且不能回滚.
– 回滚的例子:
1 2 3 4 5 6 7 8 9
| create database bank;
create table user( id int primary key, name varchar(20), money int );
insert into user values(1, 'a', 1000);
|
可以看到,在执行插入语句后数据立刻生效,原因是 MySQL 中的事务自动将它提交到了数据库中。那么所谓回滚的意思就是,撤销sql语句的执行效果。
在mysql中使用rollback
执行回滚:
1 2 3 4 5 6 7 8 9 10
| rollback;
select * from user; + | id | name | money | + | 1 | a | 1000 | +
|
那么如何设置回滚呢?
– 设置mysql自动提交为false
1 2 3 4 5 6 7 8
| set autocommit = 0;
select @@autocommit; + | @@autocommit | + | 0 | +
|
– 上面的操作,关闭了mysql的自动提交(commit)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| insert into user values(2, 'b', 1000);
select * from user; + | id | name | money | + | 1 | a | 1000 | | 2 | b | 1000 | +
rollback;
select * from user; + | id | name | money | + | 1 | a | 1000 | +
|
关闭autocommit
后,数据的变化是在一张虚拟的临时数据表中展示的,发生变化的数据并没有真正插入到数据表中.
那么如何将数据真正提交到数据库中呢,使用commit
提交:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| insert into user values(2, 'b', 1000);
commit;
rollback;
select * from user; + | id | name | money | + | 1 | a | 1000 | | 2 | b | 1000 | +
|
总结:
- 自动提交
- 查看自动提交状态:
select @@autocommit;
- 设置自动提交状态:
set autocommit = 0;
- 手动提交
@@autocommit = 0
时,使用commit;
命令手动提交事务
- 事务回滚
@@autocommit = 0
时,使用rollback
命令回滚事务
– 说回到转账事务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| update user set money = money - 100 where name = 'a';
update user set money = money + 100 where name = 'b';
select * from user; + | id | name | money | + | 1 | a | 900 | | 2 | b | 1100 | +
|
如果这时候转账出现问题,就可以通过rollback
使转账的两条语句都不成功
1 2 3 4 5 6 7 8 9 10 11
| rollback;
select * from user; + | id | name | money | + | 1 | a | 1000 | | 2 | b | 1000 | +
|
也就是说,事务给我们提供了一个可以反悔的机会。假设数据没有发生意外,这时可以通过commit
手动将数据真正提交到数据表中。
手动开启事务:begin
或者start transaction
事务的默认提交被开启(@@autocommit = 1
)后,此时就不能使用事务回滚了,但是我们还可以开启一个事务处理事件,使其可以发生回滚:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| begin; update user set money = money - 100 where name = 'a'; update user set money = money + 100 where name = 'b';
select * from user; + | id | name | money | + | 1 | a | 900 | | 2 | b | 1100 | +
rollback;
select * from user; + | id | name | money | + | 1 | a | 1000 | | 2 | b | 1000 | +
|
再次使用commit
提交数据,提交后便不能事务回滚了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| begin; update user set money = money - 100 where name = 'a'; update user set money = money + 100 where name = 'b';
select * from user; + | id | name | money | + | 1 | a | 900 | | 2 | b | 1100 | +
commit;
rollback;
select * from user; + | id | name | money | + | 1 | a | 900 | | 2 | b | 1100 | +
|
事务的四大特征 ACID
A 原子性: 事务是最小的单位,不可以再分割
C 一致性: 事务要求,同一事务中的 sql 语句,必须保证同时成功或者同时失败
I 隔离性: 事务1 和 事务2 之间是具有隔离性的.
D 持久性: 事务一旦结束(commit),就不可以返回(rollback).
事务开启:
- 修改默认提交
set autocommit = 0;
begin;
start transaction;
事务手动提交:
commit;
事务手动回滚:
rollback;
事务的隔离性(隔离级别由低到高):
read uncommitted;
(读未提交的)
read committed;
(读已经提交的)
repeatable read;
(可以重复读)
serializable;
(串行化)
1. read uncommitted;
(读未提交的)
如果有事务a和事务b,a事务对数据进行操作,在操作的过程中,事务没有被提交,但是b可以看见a操作的结果.
bank数据库 user表
1 2 3 4 5 6 7 8 9 10 11 12
| insert into user values(3, '小明',1000); insert into user values(4, '淘宝店', 1000);
select * from user; + | id | name | money | + | 1 | a | 900 | | 2 | b | 1100 | | 3 | 小明 | 1000 | | 4 | 淘宝店 | 1000 | +
|
– 如何查看数据库的隔离级别?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| mysql 8.0:
select @@global.transaction_isolation;
select @@transaction_isolation;
select @@global.transaction_isolation; + | @@global.transaction_isolation | + | REPEATABLE-READ | +
mysql 5.x: select @@global.tx_isolation; select @@tx_isolation;
|
– 如何修改隔离级别?
1 2 3 4 5 6 7 8
| set global transaction isolation level read uncommitted;
select @@global.transaction_isolation; + | @@global.transaction_isolation | + | READ-UNCOMMITTED | +
|
– 转账:小明在淘宝店买鞋子:800块钱
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| 小明->成都 ATM 淘宝店-> 广州 ATM
begin; update user set money = money - 800 where name = '小明'; update user set money = money + 800 where name = '淘宝店';
select * from user; + | id | name | money | + | 1 | a | 900 | | 2 | b | 1100 | | 3 | 小明 | 200 | | 4 | 淘宝店 | 1800 | + use bank; select * from user; + | id | name | money | + | 1 | a | 900 | | 2 | b | 1100 | | 3 | 小明 | 200 | | 4 | 淘宝店 | 1800 | +
rollback; select * from user; + | id | name | money | + | 1 | a | 900 | | 2 | b | 1100 | | 3 | 小明 | 1000 | | 4 | 淘宝店 | 1000 | +
select * from user; + | id | name | money | + | 1 | a | 900 | | 2 | b | 1100 | | 3 | 小明 | 1000 | | 4 | 淘宝店 | 1000 | +
|
– 如果两个不同的地方,都在进行操作,如果事务a开启之后,他的数据会被其他事务读取到,这样就会出现脏读
– 脏读: 一个事务读到了另外一个事务没有提交的数据,就叫做脏读
– 实际开发是不允许脏读出现的.
2. read committed;
(读已经提交的)
1 2 3 4 5 6 7 8 9 10
| set global transaction isolation level read committed;
select @@global.transaction_isolation; + | @@global.transaction_isolation | + | READ-COMMITTED | +
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| begin; select * from user; + | id | name | money | + | 1 | a | 900 | | 2 | b | 1100 | | 3 | 小明 | 1000 | | 4 | 淘宝店 | 1000 | +
begin; insert into user values(5, 'c', 100); commit; + | id | name | money | + | 1 | a | 900 | | 2 | b | 1100 | | 3 | 小明 | 1000 | | 4 | 淘宝店 | 1000 | | 5 | c | 100 | +
select avg(money) from user; + | avg(money) | + | 820.0000 | +
|
– 虽然我只能读到另一个事务提交的数据,但还是会出现问题,就是读取同一个表的数据,发现前后不一致
– 这就是不可重复读现象,在read committed的时候会出现
– 意思就是: 你刚select完,想立马计算avg,但当你输入代码的时候有人提交新数据改变了表,这就是不可重复读
3. repeatable read;
(可以重复读)
1 2 3 4 5 6 7 8 9 10
| set global transaction isolation level repeatable read;
select @@global.transaction_isolation; + | @@global.transaction_isolation | + | REPEATABLE-READ | +
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| begin; insert into user values(6, 'd', 1000); commit; select * from user; + | id | name | money | + | 1 | a | 900 | | 2 | b | 1100 | | 3 | 小明 | 1000 | | 4 | 淘宝店 | 1000 | | 5 | c | 100 | | 6 | d | 1000 | +
begin; select * from user; + | id | name | money | + | 1 | a | 900 | | 2 | b | 1100 | | 3 | 小明 | 1000 | | 4 | 淘宝店 | 1000 | | 5 | c | 100 | +
insert into user values(6, 'd', 1000); ERROR 1062 (23000): Duplicate entry '6' for key 'PRIMARY'
|
– 这种现象就叫做幻读!
– 事务a和事务b同时操作一张表,事务a提交的数据,不能被事务b读到,就可以造成幻读.
4. serializable;
(串行化)
1 2 3 4 5 6 7 8 9 10
| set global transaction isolation level serializable;
select @@global.transaction_isolation; + | @@global.transaction_isolation | + | SERIALIZABLE | +
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| begin;
begin;
insert into user values(7, '赵铁柱', 1000); commit; select * from user; + | id | name | money | + | 1 | a | 900 | | 2 | b | 1100 | | 3 | 小明 | 1000 | | 4 | 淘宝店 | 1000 | | 5 | c | 100 | | 6 | d | 1000 | | 7 | 赵铁柱 | 1000 | +
select * from user; + | id | name | money | + | 1 | a | 900 | | 2 | b | 1100 | | 3 | 小明 | 1000 | | 4 | 淘宝店 | 1000 | | 5 | c | 100 | | 6 | d | 1000 | | 7 | 赵铁柱 | 1000 | +
begin; insert into user values(8, '王小花', 1000);
begin; insert into user values(8, '王小花', 1000);
|
– 串行化问题是,性能特差
– 性能比较:
read uncommitted > read committed > repeatable read > serializable
– 隔离级别越高,性能越差
mysql默认隔离级别是 repeatable read
参考资料:
- B站视频“一天学会 MySQL 数据库”
- 大佬笔记1
- 大佬笔记2