title: 数据库-MySQL的事务
date: 2021-11-21 20:44:39
tags:


数据库-MySQL的事务

mysql中,事务其实是一个最小的不可分割的工作单元。事务能够保证一个业务的完整性。

比如银行转账,a用户的前转给b用户100元

a -> -100

update user set money = money - 100 where name = ‘a’;

b -> +100

update user set money = money + 100 where name = ‘b’;

实际的程序中,如果只有一条语句执行成功了而另一条没有执行,则会出现数据前后不一致,所以对于多条sql语句,可能会有需要同时成功的要求,要么就同时失败。

mysql中如何控制事务

1.mysql默认是开启事务的(自动提交)

select @@autocommit;

值为1就是自动提交

±-------------+
| @@autocommit |
±-------------+
| 1 |
±-------------+

默认事务开启的作用是什么?

当我们去执行一个sql语句的时候,效果就会立即体现出来,且不能回滚。

show databases;
±-------------------+
| Database |
±-------------------+
| information_schema |
| mysql |
| performance_schema |
| pet |
| selecttest |
| sys |
| testjoin |
| user |
±-------------------+

create database bank;

insert into user values(1, ‘a’, 1000);

select * from user;
±—±-----±------+
| id | name | money |
±—±-----±------+
| 1 | a | 1000 |
±—±-----±------+

插入一条数据后就立马生效了,不能回滚,相当于默认就提交了

事务回滚:撤销sql语句执行效果

rollback;

rollback;
Query OK, 0 rows affected (0.00 sec)

select * from user;
±—±-----±------+
| id | name | money |
±—±-----±------+
| 1 | a | 1000 |
±—±-----±------+

可以进行回滚的话,需要设置mysql自动提交为false

set autocommit = 0;

关闭了mysql的自动提交(commit)

select @@autocommit;

±-------------+
| @@autocommit |
±-------------+
| 0 |
±-------------+

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 |
±—±-----±------+

如果想要让虚拟的表提交怎么做?用commit语句

再一次插入数据

insert into user values(2, ‘b’, 1000);

因为把自动提交关闭了,这里是手动提交数据

commit;

再撤销是不可以撤销的(事务的持久性,就是一旦之前提交了commit,就会持久地产生效果,下面的rollback就不会产生效果)

rollback;

select * from user;
±—±-----±------+
| id | name | money |
±—±-----±------+
| 1 | a | 1000 |
| 2 | b | 1000 |
±—±-----±------+

自动提交?@@autocommit = 1;

手动提交?commit;

事务回滚?rollback;

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 |
±—±-----±------+

事务给我们提供了一个返回的机会

其他方式:

手动开启事务

begin;或start transaction;这两个sql语句都可以帮我们手动开启一个事务;

set autocommit = 1;

select @@autocommit;
±-------------+
| @@autocommit |
±-------------+
| 1 |
±-------------+

select * from user;
±—±-----±------+
| id | name | money |
±—±-----±------+
| 1 | a | 1000 |
| 2 | b | 1000 |
±—±-----±------+

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; //发现rollback并没有撤销效果
±—±-----±------+
| id | name | money |
±—±-----±------+
| 1 | a | 900 |
| 2 | b | 1100 |
±—±-----±------+

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 | 800 |
| 2 | b | 1200 |
±—±-----±------+

rollback;

select * from user;

±—±-----±------+
| id | name | money |
±—±-----±------+
| 1 | a | 900 |
| 2 | b | 1100 |
±—±-----±------+

start transaction;

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 | 800 |
| 2 | b | 1200 |
±—±-----±------+
rollback;
select * from user;
±—±-----±------+
| id | name | money |
±—±-----±------+
| 1 | a | 900 |
| 2 | b | 1100 |
±—±-----±------+

事务开启后一旦commit提交,就不可以回滚了(就是当前这个事务在提交的时候就结束了)

commit;

select * from user;
±—±-----±------+
| id | name | money |
±—±-----±------+
| 1 | a | 900 |
| 2 | b | 1100 |
±—±-----±------+
rollback;
select * from user;
±—±-----±------+
| id | name | money |
±—±-----±------+
| 1 | a | 900 |
| 2 | b | 1100 |
±—±-----±------+

事务的四大特征:ACID

A原子性:事务是最小的单位,不可以再分割

C一致性:事务要求,同一事务中的sql语句,必须保证同时成功或者同时失败

I隔离性:事务1和事务2之间是具有隔离性的

D持久性:事务一旦结束(commit,rollback二选一),就不可以返回。

事务开启:

三种方式

1.修改默认提交 set autocommit = 0;

2.begin;

3.start transaction;

事务手动提交:

commit;

事务手动回滚:

rollback;

事务的隔离性:

1.read uncommtited;----读未提交的

2.read committed;----读已经提交的

3.repeatable read;----可以重复读

4.serializable;----串行化

1.read uncommitted;----读为提交的

如果有事务a和事务b,a事务对数据进行操作,在操作的过程中,事务没有被提交,但b可以看到a操作的结果。

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 |
±—±-------±------+

如何查看数据库的隔离级别?(系统不同语句不同)

mysql 8.0:这两种都可以

select @@global.transaction_isolation; //系统级别的

或者

select @@transaction_isolation; //会话级别的

±-------------------------------+
| @@global.transaction_isolation |
±-------------------------------+
| REPEATABLE-READ |
±-------------------------------+

mysql默认隔离级别REPEATABLE-READ

mysql 5.x:

select @@global.tx_isolation; //系统级别的

或者

select @@tx_isolation; //会话级别的

如何修改隔离级别?

set global transaction isolation level read uncommitted;

select @@global.transaction_isolation;
±-------------------------------+
| @@global.transaction_isolation |
±-------------------------------+
| READ-UNCOMMITTED |
±-------------------------------+

小明在淘宝店买了一双800元的鞋子,需要转账,小明在成都,淘宝店在广州

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 |
±—±-------±------+

给淘宝店打电话说你去查一下是不是到账了,查了一下确实到账了

rollback;

之后钱就回来了

select * from user;
±—±-------±------+
| id | name | money |
±—±-------±------+
| 1 | a | 900 |
| 2 | b | 1100 |
| 3 | 小明 | 1000 |
| 4 | 淘宝店 | 1000 |
±—±-------±------+

如果两个不同的地方都在进行操作,如果事务a开启之后,它的数据可被其他事务读取到,这样就会出现脏读(一个事务读到了另一个事务没有提交的数据),就像上面的例子,实际开发是不允许脏读出现的。

2.read committed;----读已经提交的

set global transaction isolation level read committed;

select @@global.transaction_isolation;
±-------------------------------+
| @@global.transaction_isolation |
±-------------------------------+
| READ-COMMITTED |
±-------------------------------+

一个事务只能读取另一个事务已经提交的数据

小张是银行的会计

start transaction;

select * from user;
±—±-------±------+
| id | name | money |
±—±-------±------+
| 1 | a | 900 |
| 2 | b | 1100 |
| 3 | 小明 | 1000 |
| 4 | 淘宝店 | 1000 |
±—±-------±------+

小张出去了

另一个城市的小王插入了一个数据并提交了

start transaction;

insert into user values(5, ‘c’, 100);

select * from user;
±—±-------±------+
| id | name | money |
±—±-------±------+
| 1 | a | 900 |
| 2 | b | 1100 |
| 3 | 小明 | 1000 |
| 4 | 淘宝店 | 1000 |
| 5 | c | 100 |
±—±-------±------+

commit;

小张回来了需要算平均值,发现平均值不是他走之前的数据该有的平均值了

select avg(money) from user;
±-----------+
| avg(money) |
±-----------+
| 820.0000 |
±-----------+

虽然我只能读到另一个事务提交的数据,但是还是会出现问题,就是读取同一个表的数据,发现前后不一致,这就是不可重复读现象(read committed)

3.repeatable read;----可以重复读(这是mysql默认模式)

set global transaction isolation level repeatable read;

select @@global.transaction_isolation;
±-------------------------------+
| @@global.transaction_isolation |
±-------------------------------+
| REPEATABLE-READ |
±-------------------------------+

select * from user;
±—±-------±------+
| id | name | money |
±—±-------±------+
| 1 | a | 900 |
| 2 | b | 1100 |
| 3 | 小明 | 1000 |
| 4 | 淘宝店 | 1000 |
| 5 | c | 100 |
±—±-------±------+

张全蛋–成都

start transaction;

王尼玛–北京

start transaction;

张全蛋–成都

insert into user values(6, ‘d’, 1000);

王尼玛–北京

select * from user;

±—±-------±------+
| id | name | money |
±—±-------±------+
| 1 | a | 900 |
| 2 | b | 1100 |
| 3 | 小明 | 1000 |
| 4 | 淘宝店 | 1000 |
| 5 | c | 100 |
±—±-------±------+

张全蛋–成都

commit;

王尼玛–北京,这边还是没有

select * from user;

±—±-------±------+
| id | name | money |
±—±-------±------+
| 1 | a | 900 |
| 2 | b | 1100 |
| 3 | 小明 | 1000 |
| 4 | 淘宝店 | 1000 |
| 5 | c | 100 |
±—±-------±------+

张全蛋–成都,但是这边有

select * from user;
±—±-------±------+
| id | name | money |
±—±-------±------+
| 1 | a | 900 |
| 2 | b | 1100 |
| 3 | 小明 | 1000 |
| 4 | 淘宝店 | 1000 |
| 5 | c | 100 |
| 6 | d | 1000 |
±—±-------±------+

王尼玛–北京,再去插入就会报错

insert into user values(6, ‘d’, 1000); //报错

ERROR 1062 (23000): Duplicate entry ‘6’ for key ‘user.PRIMARY’

王尼玛这边明明没有6号却显示6号已经存在

这种现象叫做幻读,就是事务a和事务b同时操作一张表,事务a提交的数据也不能被事务b读到,就造成了幻读

4.serializable;----串行化

set global transaction isolation level serializable;

select @@global.transaction_isolation;
±-------------------------------+
| @@global.transaction_isolation |
±-------------------------------+
| SERIALIZABLE |
±-------------------------------+

select * from user;
±—±-------±------+
| id | name | money |
±—±-------±------+
| 1 | a | 900 |
| 2 | b | 1100 |
| 3 | 小明 | 1000 |
| 4 | 淘宝店 | 1000 |
| 5 | c | 100 |
| 6 | d | 1000 |
±—±-------±------+

张全蛋–成都

start transaction;

王尼玛–北京

start transaction;

张全蛋–成都

insert into user values(7, ‘赵铁柱’, 1000);

王尼玛–北京

select * from user;
±—±-------±------+
| id | name | money |
±—±-------±------+
| 1 | a | 900 |
| 2 | b | 1100 |
| 3 | 小明 | 1000 |
| 4 | 淘宝店 | 1000 |
| 5 | c | 100 |
| 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 |
| 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 |
±—±-------±------+

张全蛋–成都

insert into user values(8, ‘王小花’, 1000);

此时发现sql语句会卡住不执行,为什么会卡住?

当user表被另外一个事务操作的时候,其他事务里面的写操作是不可以进行的,因为王尼玛–北京start transaction;开启了事务后,王尼玛进行了user表的操作select * from user;这两个事务是串行的,必须等王尼玛这边committed;提交了,张全蛋这边才可以继续操作(前提是王尼玛执行的语句没有等待超时)

串行化的问题:性能特差

性能比较:隔离级别越高性能越差,mysql默认隔离级别是repeatable read;

read uncommtited;>read committed>repeatable read;>serializable;----串行化