【MySQL】深入浅出事务:保证数据一致性的核心武器

【MySQL】深入浅出事务:保证数据一致性的核心武器

个人主页:♡喜欢做梦

欢迎  👍点赞  ➕关注  ❤️收藏  💬评论


目录

🍓一、什么是事务?

🍡1.定义

🍡2.事务的ACID特性

🍑原子性(Atomicity)

🍑一致性(Consistency)

🍑隔离性(Isolation)

🍑持久性(Durablity)

🍡3.如何使用事务?

🍠事务的控制的核心语句

🍠事务的使用

🍐回滚的使用

🍐提交的使用

🍐保存点的使用

 🍠自动/手动提交事务

🍎查看事务提交是自动/手动

🍎设置事务自动/手动

🍎手动的使用

🍎手动和自动的区别:

🍓二、事务的隔离级别

🍨1.事务并发执行可能引发的问题

🍉脏读(Dirty Read)

🍉不可重复读(Non-Repeatable Read)

🍉幻读(Phantom Read)

🍨2.查看和设置隔离级别

🍍查看隔离级别

🍍隔离级别

🍊设置隔离级别

🍊隔离级别

🍅读未提交(Read Un***mitted)

🍅读已提交(Read ***mitted)

🍅可重复读(Repeatable Read)

🍅串行化(Serializable)


🍓一、什么是事务?

🍡1.定义

事务是把一组SQL语句打包成一个整体,在这组SQL语句的执行过程中,要么全部成功,要么全部失败。这组SQL语句可以是一条也可以是多条。

示例:

在生活中,我们经常会转账给别人。以下举例,小王和小明的钱包余额均为200元,这时小王要转账给小明100元,这种操作必须两个要么一起成功,要么一起失败。否则结果可能会导致,小王转账成功了,而小明接收转账失败了,这种情况就是导致金钱丢失。为了避免这种情况,我们就需要将这一组SQL语句打包成一个整体,也就是放在事务中,利用事务的特性性来保证该操作过程要么全部成功,要么全部失败。

什么是事务的特性呢?接下来就要介绍事务的ACID特性了。 

🍡2.事务的ACID特性

🍑原子性(Atomicity)

含义:事务中的所有操作就是一个不可分割的整体,像原子一样。这些操作,要么全部成功,要么全部失败。数据库会记录事务执行前的数据状态,一旦事务执行过程出现失败,就会回滚到原来的初始状态。

🍑一致性(Consistency)

含义:事务执行前后,事务的完整性不会被破坏。事务执行完成之后,保证数据正确并且符合预期。

🍑隔离性(Isolation)

含义:数据库允许多个并发事务同时对数据进行修改和读写,隔离性可以保证多个事务并发执行,并且不相互干扰。

🍑持久性(Durablity)

含义:事务一旦成功提交,就会保存到存储介质中,并永久保存,不论数据库损坏,也不会发生丢失。

🍡3.如何使用事务?

查看MySQl支持的存储引擎

我们在使用事务之前要查看一下,MySQL是否支持事务。在MySQl中支持事务的存储引擎是InnoBD

语法 

show engines;

如图: 

🍠事务的控制的核心语句

语法

-- 开启一个事务
-- 方式1:
start transaction;

-- 方式2:
begin;

-- 根据执行结果决定提交还是回滚事务
-- 提交事务,使所有操作生效,并对更改持久化
***mit;

-- 回滚当前事务,撤销所有操作,回到原来的状态
rollback;
  • 开启事务后,所写的SQL语句就包含在事务中,都具有ACID特性;
  • 无论提交事务还是回滚事务,事务都会关闭;
  • ***mit之后也就是提交之后,事务就不能回滚了。

🍠事务的使用

🍐回滚的使用

含义:回滚是事务撤销修改并结束事务。

 修改数据后回滚到原来状态:

-- 查询数据
mysql> select* from a***ount;
+----+--------+-------+
| id | name   | money |
+----+--------+-------+
|  1 | 小王   |   200 |
|  2 | 小明   |   200 |
+----+--------+-------+
2 rows in set (0.00 sec)

-- 开始事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

-- 转账:小王转账100给小明
mysql>  update a***ount set money=money-100 where name='小王';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update a***ount set money=money+100 where name='小明';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

-- 查询转账后的结果
mysql>  select* from a***ount;
+----+--------+-------+
| id | name   | money |
+----+--------+-------+
|  1 | 小王   |   100 |
|  2 | 小明   |   300 |
+----+--------+-------+
2 rows in set (0.00 sec)

-- 回滚,数据回到原来的状态
mysql> rollback;
Query OK, 0 rows affected (0.06 sec)

查询数据:
mysql>  select* from a***ount;
+----+--------+-------+
| id | name   | money |
+----+--------+-------+
|  1 | 小王   |   200 |
|  2 | 小明   |   200 |
+----+--------+-------+
2 rows in set (0.00 sec)

🍐提交的使用

含义: 提交是事务的更改生效并结束事务。

 修改数据后提交事务

-- 开始事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

-- 再次执行转账操作
mysql> update a***ount set money=money-100 where name='小王';
Query OK, 1 row affected (0.09 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update a***ount set money=money+100 where name='小明';
Query OK, 1 row affected (0.04 sec)
Rows matched: 1  Changed: 1  Warnings: 0

-- 提交事务
mysql> ***mit;
Query OK, 0 rows affected (0.00 sec)

-- 提交事务后的数据
mysql> select* from a***ount;
+----+--------+-------+
| id | name   | money |
+----+--------+-------+
|  1 | 小王   |   100 |
|  2 | 小明   |   300 |
+----+--------+-------+
2 rows in set (0.00 sec)

-- 提交后尝试回滚
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)

-- 回滚失败:提交事务后,事务关闭,数据落盘
mysql> select* from a***ount;
+----+--------+-------+
| id | name   | money |
+----+--------+-------+
|  1 | 小王   |   100 |
|  2 | 小明   |   300 |
+----+--------+-------+
2 rows in set (0.00 sec)
  • 事务回滚或提交后,事务关闭。也就是说事务回滚后,提交无效。事务提交后,回滚无效。
  • 回滚会回到数据最初的状态。 

无论我们修改多少次数据,我们使用回滚都会回到最初的状态。如果我们想要数据回滚到某一个状态,我们应该怎么办?这时候我们需要使用保存点 ,有了保存点,当我们回滚时,可以只回滚到某个保存点。

🍐保存点的使用

含义:当回滚时,可以只回滚到某个保存点,而不是最初状态。

语法:

设置保存点

savepoint 保存点名称;

回滚到保存点

rollback to 保存点名称;

释放保存点

release savepoint 保存点名称;

使用: 

-- 开启事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

-- 转账1:小王向小明转账100
mysql> update a***ount set  money=money-100 where name='小王';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update a***ount set  money=money+100 where name='小明';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

-- 查询转账后的结果
mysql>  select* from a***ount;
+----+--------+-------+
| id | name   | money |
+----+--------+-------+
|  1 | 小王   |   100 |
|  2 | 小明   |   300 |
+----+--------+-------+
2 rows in set (0.00 sec)

-- 设置保存点
mysql> savepoint money1;
Query OK, 0 rows affected (0.00 sec)

-- 转账2:小王再次向小明转账50
mysql>  update a***ount set  money=money-50 where name='小王';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update a***ount set  money=money+50 where name='小明';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

-- 查询转账结果
mysql>  select* from a***ount;
+----+--------+-------+
| id | name   | money |
+----+--------+-------+
|  1 | 小王   |    50 |
|  2 | 小明   |   350 |
+----+--------+-------+
2 rows in set (0.00 sec)

-- 返回到第一个保存点的数据结果
mysql> rollback to money1;
Query OK, 0 rows affected (0.00 sec)

-- 查看
mysql> select* from a***ount;
+----+--------+-------+
| id | name   | money |
+----+--------+-------+
|  1 | 小王   |   100 |
|  2 | 小明   |   300 |
+----+--------+-------+
2 rows in set (0.00 sec)

-- 回滚保存点,事务不会关闭
-- 再次尝试转账,回滚到保存点
mysql> update a***ount set  money=money-50 where name='小王';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update a***ount set  money=money+50 where name='小明';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select* from a***ount;
+----+--------+-------+
| id | name   | money |
+----+--------+-------+
|  1 | 小王   |    50 |
|  2 | 小明   |   350 |
+----+--------+-------+
2 rows in set (0.00 sec)

mysql>  rollback to money1;
Query OK, 0 rows affected (0.00 sec)

-- 回滚到保存点成功
mysql> select* from a***ount;
+----+--------+-------+
| id | name   | money |
+----+--------+-------+
|  1 | 小王   |   100 |
|  2 | 小明   |   300 |
+----+--------+-------+
2 rows in set (0.00 sec)

mysql> update a***ount set  money=money-50 where name='小王';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update a***ount set  money=money+50 where name='小明';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

-- 释放保存点
mysql> release savepoint money1;
Query OK, 0 rows affected (0.00 sec)

-- 再次使用保存点失败:保存点释放后不能再使用
mysql> rollback to money1;
ERROR 1305 (42000): SAVEPOINT money1 does not exist

-- 回滚到最初状态
mysql> rollback;
Query OK, 0 rows affected (0.04 sec)

mysql> select* from a***ount;
+----+--------+-------+
| id | name   | money |
+----+--------+-------+
|  1 | 小王   |   200 |
|  2 | 小明   |   200 |
+----+--------+-------+
2 rows in set (0.00 sec)
  • 在同一事务中,保存点名称是唯一的。如果设置相同名称的保存点名称,后设置的保存点会覆盖先设置的;
-- 开启事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

-- 转账100
mysql> update a***ount set  money=money-100 where name='小王';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update a***ount set  money=money+100 where name='小明';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

-- 查询结果
mysql> select* from a***ount;
+----+--------+-------+
| id | name   | money |
+----+--------+-------+
|  1 | 小王   |   100 |
|  2 | 小明   |   300 |
+----+--------+-------+
2 rows in set (0.00 sec)

-- 先设置保存点的名称
mysql> savepoint money1;
Query OK, 0 rows affected (0.00 sec)

-- 转账50
mysql> update a***ount set  money=money-50 where name='小王';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update a***ount set  money=money+50 where name='小明';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

-- 查询结果
mysql> select* from a***ount;
+----+--------+-------+
| id | name   | money |
+----+--------+-------+
|  1 | 小王   |    50 |
|  2 | 小明   |   350 |
+----+--------+-------+
2 rows in set (0.00 sec)

-- 后设置相同名称的保存点
mysql> savepoint money1;
Query OK, 0 rows affected (0.00 sec)

-- 转账25
mysql> update a***ount set  money=money-25 where name='小王';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update a***ount set  money=money+25 where name='小明';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select* from a***ount;
+----+--------+-------+
| id | name   | money |
+----+--------+-------+
|  1 | 小王   |    25 |
|  2 | 小明   |   375 |
+----+--------+-------+
2 rows in set (0.00 sec)

-- 回滚到保存点money1
mysql> rollback to  savepoint money1;
Query OK, 0 rows affected (0.00 sec)

-- 回滚到后设置的保存点位置
mysql> select* from a***ount;
+----+--------+-------+
| id | name   | money |
+----+--------+-------+
|  1 | 小王   |    50 |
|  2 | 小明   |   350 |
+----+--------+-------+
2 rows in set (0.00 sec)
  • 回滚保存点不会关闭事务。

 🍠自动/手动提交事务

默认情况下,MySQL采用自动提交事务模式,也就是说我们执行每个修改操作,比如插入、删除,都会自动开启一个事务并在语句执行完成之后自动提交,发生异常事自动回滚。

🍎查看事务提交是自动/手动

语法

show variables like 'auto***mit';
  • auto***mmit:系统变量,标识事务是否自动提交。 

示例: 

mysql> show variables like 'auto***mit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| auto***mit    | ON    | -- ON表示自动提交开启
+---------------+-------+
1 row in set, 1 warning (0.02 sec)
🍎设置事务自动/手动

设置自动

-- 方式一:
set auto***mit=1;
-- 方式二:
set auto***mit=ON;

设置手动

方式一:
set auto***mit=0;
方式二:
set auto***mit=off;

示例:设置手动

mysql> set auto***mit=0;
Query OK, 0 rows affected (0.00 sec)

mysql> set auto***mit=OFF;
Query OK, 0 rows affected (0.00 sec)

mysql> show variables like 'auto***mit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| auto***mit    | OFF   |
+---------------+-------+
1 row in set, 1 warning (0.00 sec)
🍎手动的使用
-- 设置手动:
mysql> set auto***mit=0;
Query OK, 0 rows affected (0.00 sec)

-- 不用显示开启事务
-- 转账
mysql> update a***ount set  money=money-100 where name='小王';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update a***ount set  money=money+100 where name='小明';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

查询
mysql>  select* from a***ount;
+----+--------+-------+
| id | name   | money |
+----+--------+-------+
|  1 | 小王   |   100 |
|  2 | 小明   |   300 |
+----+--------+-------+
2 rows in set (0.00 sec)

-- 回滚
mysql> rollback;
Query OK, 0 rows affected (0.05 sec)

mysql> select* from a***ount;
+----+--------+-------+
| id | name   | money |
+----+--------+-------+
|  1 | 小王   |   200 |
|  2 | 小明   |   200 |
+----+--------+-------+
2 rows in set (0.00 sec)
🍎手动和自动的区别:
  • 自动要开启事务(start transaction/begin),通过***mit或rollback结束事务 
  • 手动不用显示开启事务,通过***mit或rollback结束事务 

🍓二、事务的隔离级别

事务具有隔离性,前面也有提到,就是控制多个事务并发执行相互影响的机制。事务之间具有不同程度的隔离称为事务的隔离级别。不同的隔离级别可能会引发不同的情况,有脏读、幻读、不可重复读的问题。这里先说一下,什么是脏读、幻读、和不可重复读。

🍨1.事务并发执行可能引发的问题

🍉脏读(Dirty Read)

        一个事务读取另一个未提交事务的数据。例如,你原本打算给一个人转账500元,你还没发出去,你朋友看到了,以为你要转账得金额是500,后面,你又不想只转账,你最后转账了1000。所以对方读的是你还未提交的数据。

🍉不可重复读(Non-Repeatable Read)

        在同一个事务中,多次读取一个事务的某一行数据可能会得到不同的结果。例如,还是刚刚那个朋友,他原本以为的是你要转账得500,结果第二次又去看,看到的是,你给对方发的1000。这个第一次读的结果与第二次不一样。

🍉幻读(Phantom Read)

       在一个事务中,两次执行相同的查询,得到的结果集不同。例如,班上原本只有500个人,后面又新来了一个人。

🍨2.查看和设置隔离级别

在MySQL中InnoDB引擎中事务的隔离级别分为4种,分别有读未提交、读已提交、可重复读、串行化。

🍍查看隔离级别

事务的隔离级别分为全局作用域会话作用域

查看作用域和隔离级别语法

-- 全局作用域
select @@global.transaction_isolation;

-- 会话作用域
select @@session.transaction_isolation;

示例:

-- 全局作用域
mysql> select @@global.transaction_isolation;
+--------------------------------+
| @@global.transaction_isolation |
+--------------------------------+
| REPEATABLE-READ                |-- 隔离级别默认是可重复读
+--------------------------------+
1 row in set (0.00 sec)

-- 会话作用域
mysql> select @@session.transaction_isolation;
+---------------------------------+
| @@session.transaction_isolation |
+---------------------------------+
| REPEATABLE-READ                 |
+---------------------------------+
1 row in set (0.00 sec)
  • 全局级:对新创建的所有会话都采用隔离级别;
  • 会话级:只对当前数据库有效,不会影响其他会话
  • @@表示查看系统变量 ;

🍍隔离级别

🍊设置隔离级别

语法

-- 方式一
set [session|global] transaction isolation level 隔离级别名称|访问模式;

-- 方式二
set [session|global] transaction_isolation = '隔离级别名称';

-- 方式三
set session|global.transaction_isolation = '隔离级别名称';

示例:

-- 设置为读未提交
-- 方式一
mysql> set transaction isolation level read un***mitted;
Query OK, 0 rows affected (0.00 sec)
-- 方式二
mysql> set transaction_isolation = 'read-un***mitted';
Query OK, 0 rows affected (0.00 sec)
-- 方式三
mysql> set @@session.transaction_isolation = 'read-un***mitted';
Query OK, 0 rows affected (0.00 sec)
  • 后面两种方式遇到空格要用“-”代替; 
  • a***ess_mode(访问模式):只读(read only)、读写(read write,默认)
  • 隔离级别分明四种:读未提交(Read Un***mitted)、读已提交(Read ***mitted)、可重复读(Repeatable Read)、串行化(Serializable)
🍊隔离级别
🍅读未提交(Read Un***mitted)

含义: 允许一个事务读取另一个未提交事务修改的数据,可能出现脏读、不可重复读、幻读的问题。

🍅读已提交(Read ***mitted)

含义:事务只能读取其他事务已经提交的数据,避免了脏读,但可能出现幻读和不可重复读的问题。

🍅可重复读(Repeatable Read)

含义:在同一个事务中,多次读取同一个数据结果是一致的,避免了脏读和不和重复读,但仍然可能出现幻读的情况。

🍅串行化(Serializable)

 含义:最高的隔离级别,事务只能串行执行,即一个事务完毕后,才能执行另一个事务。可以完全避免脏读、不可重复读、幻读问题,当会极大降低并发性能。例如,你去厕所只有一个坑位,你上完厕所,下一个才能进去。

  •  在安全性方面:读未提交<读已提交<可重复读<串行化。
  • 在并发性能方面:读未提交>读已提交>可重复读>串行化。并发性能是指数据库在同一时刻能够处理多个并发事务的能力。
转载请说明出处内容投诉
CSS教程网 » 【MySQL】深入浅出事务:保证数据一致性的核心武器

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买