MySQL教程 MySQL教程MySQL 连接MySQL 查看数据库MySQL 创建数据库MySQL 修改数据库MySQL 删除数据库MySQL 选择数据库MySQL 数据类型MySQL存储引擎MySQL 数据表的增删改查MySQL 表数据增删改查操作MySQL 条件子句MySQL 清空表记录MySQL 模糊查询MySQL 处理重复数据MySQL 别名MySQL 限制查询结果数量Mysql 查询结果排序MySQL 范围查询MySQL 空值查询MySQL 查询结果合拼MySQL 分组MySQL 筛选分组MySQL 表连接MySQL NULL值处理MySQL 子查询MySQL 正则表达式MySQL 视图MySQL 索引MySQL 事务MySQL 数据库备份(导出)/还原(导入)MySQL安装 MySQL Windows版下载教程MySQL windows系统安装教程MySQL windows系统简单的安装教程MySQL Linux系统安装教程MySQL Linux系统下载教程MySQL Linux系统简单的安装教程配置MySQL环境变量

MySQL 事务

什么是事务?

数据库中的事务是指对数据库执行一批操作,在同一个事务当中,这些操作最终要么全部执行成功,要么全部失败,不会存在部分成功的情况。

事务是一个原子操作。是一个最小执行单元。可以甶一个或多个SQL语句组成

在同一个事务当中,所有的SQL语句都成功执行时,整 个事务成功,有一个SQL语句执行失败,整个事务都执行失败。

举个例子

比如A用户给B用户转账100操作,过程如下:

如果在事务的支持下,上面最终只有2种结果:

如果没有事务的支持,可能出现错:A账户减少了100,此时系统挂了,导致B账户没有加上100,而A账户凭空少了100。

事务特性

事务控制语句:

360截图20240507111613983.jpg

MYSQL 事务处理主要有两种方法:

1、用 BEGIN, ROLLBACK, COMMIT 来实现

2、直接用 SET 来改变 MySQL 的自动提交模式:

BEGIN 或 START TRANSACTION -- 用于开始一个事务:

BEGIN; -- 或者使用 START TRANSACTION;

COMMIT -- 用于提交事务,将所有的修改永久保存到数据库:

COMMIT;

ROLLBACK -- 用于回滚事务,撤销自上次提交以来所做的所有更改:

ROLLBACK;

SAVEPOINT -- 用于在事务中设置保存点,以便稍后能够回滚到该点:

SAVEPOINT savepoint_name;

ROLLBACK TO SAVEPOINT -- 用于回滚到之前设置的保存点:

ROLLBACK TO SAVEPOINT savepoint_name;

示例

-- 开始事务
START TRANSACTION;

-- 执行一些SQL语句
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;

-- 判断是否要提交还是回滚
IF (条件) THEN
    COMMIT; -- 提交事务
ELSE
    ROLLBACK; -- 回滚事务
END IF;

方式1:

语法:

-- 设置不自动提交事务
set autocommit=0;
-- 执行事务操作
commit|rollback;

提交事务操作,如下:

mysql> create table test1 (a int);

mysql> select * from test1;
Empty set (0.00 sec)

mysql> set autocommit=0;

mysql> insert into test1 values(1);

mysql> commit;

mysql> select * from test1;
+------+
| a   |
+------+
|   1 |
+------+

示例2:回滚事务操作,如下:

mysql> set autocommit=0;

mysql> insert into test1 values(2);

mysql> rollback;

mysql> select * from test1;
+------+
| a   |
+------+
|   1 |
+------+

方式2:

语法:

-- 开启事务
start transaction;
-- 执行事务操作
commit|rollback;

示例1:提交事务操作,如下:

mysql> select * from test1;
+------+
| a   |
+------+
|   1 |
+------+

mysql> start transaction;

mysql> insert into test1 values (2);
mysql> insert into test1 values (3);

mysql> commit;

mysql> select * from test1;
+------+
| a   |
+------+
|   1 |
|   2 |
|   3 |
+------+

示例2:回滚事务操作,如下:

mysql> select * from test1;
+------+
| a   |
+------+
|   1 |
|   2 |
|   3 |
+------+

mysql> start transaction;

mysql> delete from test1;

mysql> rollback;

mysql> select * from test1;
+------+
| a   |
+------+
|   1 |
|   2 |
|   3 |
+------+

事务中的一些问题

脏读

一个事务在执行的过程中读取到了其他事务还没有提交的数据。 这个还是比较好理解的。

两个事务同时操作同一数据,A事务对该数据进行了修改还没提交的时候,B事务访问了该条事务,并且使用了该数据,此时A事务回滚,那么B事务读到的就是脏数据。

比如事务1,修改了某个数据 事务2,刚好访问了事务1修改后的数据

此时事务1,回滚了操作 事务2,读到还是回滚前的数据

不可重复读

在同一事务中,多次读取同一数据返回的结果有所不同,换句话说,后续读取可以读到另一事务已提交的更新数据。相反,“可重复读” 在同一事务中多次读取数据时, 能够保证所读数据一样, 也就是后续读取不能读到另一事务已提交的更新数据。

这种情况发生 在一个事务内多次读同一数据。A事务查询某条数据,该事务未结束时,B事务也访问同一数据并进行了修改。那么在A事务中的两 次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。

事务1,查询某个数据 事务2,修改了某个数据,提交

事务1,再次查询这个数据

这样事务1两次查询的数据不一样,称为不可重复读

幻读

脏读、不可重复读、可重复读、幻读,其中最难理解的是幻读

1、幻读现象例子:

2、看第二种解释:

事务A在操作一堆数据的时候,事务B插入了一条数据,A事务再次(第二次)查询,发现多了一条数据,像是幻觉。与不可重复读类似,不同的是一个是修改删除操作,一个是新增操作。

事务的隔离级别

当多个事务同时进行的时候,如何确保当前事务中数据的正确性,比如A、B两个事物同时进行的时候,A是否可以看到B已提交的数据或者B未提交的数据,这个需要依靠事务的隔离级别来保证,不同的隔离级别中所产生的效果是不一样的。

隔离级别分为4种

上面4中隔离级别越来越强,会导致数据库的并发性也越来越低。

查看隔离级别

mysql> show variables like 'transaction_isolation';
+-----------------------+----------------+
| Variable_name     | Value      |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+----------------+

隔离级别的设置

分2步骤,修改文件、重启mysql,如下:

修改mysql中的my.init文件,我们将隔离级别设置为:READ-UNCOMMITTED,如下:

# 隔离级别设置
READ-UNCOMMITTED 读未提交, READ-COMMITTED 读已提交, REPEATABLE-READ 可重复读,SERIALIZABLE 串行

transaction-isolation=READ-UNCOMMITTED

各种隔离级别中会出现的问题

隔离级别脏读可能性不可重复读可能性幻读可能性
READ-UNCOMMITTED
READ-COMMITTED
REPEATABLE-READ
SERIALIZABLE

READ-UNCOMMITTED:读未提交

按时间顺序在2个窗口中执行下面操作:

时间窗口A窗口B
T1start transaction;
T2select * from test1;
T3
start transaction;
T4
insert into test1 values (1);
T5
select * from test1;
T6select * from test1;
T7
commit;
T8commit;

看一下:

T2-A:无数据,T6-A:有数据,T6时刻B还未提交,此时A已经看到了B插入的数据,说明出现了脏读。

T2-A:无数据,T6-A:有数据,查询到的结果不一样,说明不可重复读。

结论:读未提交情况下,可以读取到其他事务还未提交的数据,多次读取结果不一样,出现了脏读、不可重复读、幻读

READ-COMMITTED:读已提交

将隔离级别置为READ-COMMITTED

# 隔离级别设置
READ-UNCOMMITTED读未提交,READ-COMMITTED读已提交,REPEATABLE-READ可重复读,SERIALIZABLE串行

transaction-isolation=READ-COMMITTED

清空表,按时间顺序在2个窗口中执行下面操作:

时间窗口A窗口B
T1start transaction;
T2select * from test1;
T3
start transaction;
T4
insert into test1 values (1);
T5
select * from test1;
T6select * from test1;
T7
commit;
T8select * from test1;
T9commit;

A窗口如下:

mysql> start transaction;

mysql> select * from test1;
Empty set (0.00 sec)

mysql> select * from test1;
Empty set (0.00 sec)

mysql> select * from test1;
+------+
| a   |
+------+
|   1 |
+------+

mysql> commit;

B窗口如下:

mysql> start transaction;

mysql> insert into test1 values (1);

mysql> select * from test1;
+------+
| a   |
+------+
|   1 |
+------+

mysql> commit;

看一下:

T5-B:有数据,T6-A窗口:无数据,A看不到B的数据,说明没有脏读。

T6-A窗口:无数据,T8-A:看到了B插入的数据,此时B已经提交了,A看到了B已提交的数据,说明可以读取到已提交的数据。

T2-A、T6-A:无数据,T8-A:有数据,多次读取结果不一样,说明不可重复读。

结论:读已提交情况下,无法读取到其他事务还未提交的数据,可以读取到其他事务已经提交的数据,多次读取结果不一样,未出现脏读,出现了读已提交、不可重复读、幻读

REPEATABLE-READ:可重复读

# 隔离级别设置
READ-UNCOMMITTED读未提交,READ-COMMITTED读已提交,REPEATABLE-READ可重复读,SERIALIZABLE串行

transaction-isolation=REPEATABLE-READ

清空表,按时间顺序在2个窗口中执行下面操作:

时间窗口A窗口B
T1start transaction;
T2select * from test1;
T3
start transaction;
T4
insert into test1 values (1);
T5
select * from test1;
T6select * from test1;
T7
commit;
T8select * from test1;
T9commit;
T10select * from test1;

A窗口如下:

mysql> start transaction;

mysql> select * from test1;
Empty set (0.00 sec)

mysql> select * from test1;
Empty set (0.00 sec)

mysql> select * from test1;
Empty set (0.00 sec)

mysql> commit;

mysql> select * from test1;
+------+
| a   |
+------+
|   1 |
+------+

B窗口如下:

mysql> start transaction;

mysql> insert into test1 values (1);

mysql> select * from test1;
+------+
| a   |
+------+
|   1 |
+------+

看一下:

T2-A、T6-A窗口:无数据,T5-B:有数据,A看不到B的数据,说明没有脏读。

T8-A:无数据,此时B已经提交了,A看不到B已提交的数据,A中3次读的结果一样都是没有数据的,说明可重复读。

结论:可重复读情况下,未出现脏读,未读取到其他事务已提交的数据,多次读取结果一致,即可重复读。

幻读演示

将隔离级别置为REPEATABLE-READ,准备数据

mysql> create table t_user(id int primary key,name varchar(16) unique key);
Query OK, 0 rows affected (0.01 sec)

mysql> insert into t_user values (1,'路人甲Java'),(2,'路人甲Java');
ERROR 1062 (23000): Duplicate entry '路人甲Java' ***\*for\**** key 'name'

mysql> select * from t_user;
Empty set (0.00 sec)

按时间顺序在2个窗口中执行下面操作:

时间窗口A窗口B
T1start transaction;
T2
start transaction;
T3
– 插入路人甲Java
insert into t_user values (1,‘路人甲Java’);
T4
select * from t_user;
T5– 查看路人甲Java是否存在
select * from t_user where name=‘路人甲Java’;

T6
commit;
T7– 插入路人甲Java
insert into t_user values (2,‘路人甲Java’);

T8– 查看路人甲Java是否存在
select * from t_user where name=‘路人甲Java’;

T9commit;

A窗口如下:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t_user where name='路人甲Java';
Empty set (0.00 sec)

mysql> insert into t_user values (2,'路人甲Java');
ERROR 1062 (23000): Duplicate entry '路人甲Java' ***\*for\**** key 'name'
mysql> select * from t_user where name='路人甲Java';
Empty set (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

B窗口如下:

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into t_user values (1,'路人甲Java');
Query OK, 1 row affected (0.00 sec)

mysql> select * from t_user;
+----+---------------+
| id | name      |
+----+---------------+
|  1 | 路人甲Java   |
+----+---------------+
1 row in set (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

A想插入数据路人甲Java,插入之前先查询了一下(T5时刻)该用户是否存在,发现不存在,然后在T7时刻执行插入,报错了,报数据已经存在了,因为T6时刻B已经插入了路人甲Java。

然后A有点郁闷,刚才查的时候不存在的,然后A不相信自己的眼睛,又去查一次(T8时刻),发现路人甲Java还是不存在的。

此时A心里想:数据明明不存在啊,为什么无法插入呢?这不是懵逼了么,A觉得如同发生了幻觉一样。

SERIALIZABLE:串行

SERIALIZABLE会让并发的事务串行执行(多个事务之间读写、写读、写写会产生互斥,效果就是串行执行,多个事务之间的读读不会产生互斥)。事务A中先读取操作,事务B发起写入操作,事务A中的读取会导致事务B中的写入处于等待状态,直到A事务完成为止。串行的限制仅仅限制该表,对其它事务操作其它表不相关。简单的说就是如果事务操作表发生改变则后面的事务增删改查需要等待(非事务增删改查不受影响)

设置串行

# 隔离级别设置
READ-UNCOMMITTED读未提交,READ-COMMITTED读已提交,REPEATABLE-READ可重复读,SERIALIZABLE串行

transaction-isolation=SERIALIZABLE

清空表,按时间顺序在2个窗口中执行下面操作:


时间窗口A窗口B
T1start transaction;
T2select * from test1;
T3
start transaction;
T4
insert into test1 values (1);
T5commit;
T6
commit;


按时间顺序运行上面的命令,会发现T4-B这样会被阻塞,直到T5-A执行完毕。

上面这个演示的是读写互斥产生的效果,大家可以自己去写一下写读、写写互斥的效果。

可以看出来,事务只能串行执行了。串行情况下不存在脏读、不可重复读、幻读的问题了。