> 技术文档 > 【MySQL】深入浅出事务:保证数据一致性的核心武器_一致性英文mysql

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

个人主页:♡喜欢做梦

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


目录

🍓一、什么是事务

🍡1.定义

🍡2.事务的ACID特性

🍑原子性(Atomicity)

🍑一致性(Consistency)

🍑隔离性(Isolation)

🍑持久性(Durablity)

🍡3.如何使用事务?

🍠事务的控制的核心语句

🍠事务的使用

🍐回滚的使用

🍐提交的使用

🍐保存点的使用

 🍠自动/手动提交事务

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

🍎设置事务自动/手动

🍎手动的使用

🍎手动和自动的区别:

🍓二、事务的隔离级别

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

🍉脏读(Dirty Read)

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

🍉幻读(Phantom Read)

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

🍍查看隔离级别

🍍隔离级别

🍊设置隔离级别

🍊隔离级别

🍅读未提交(Read Uncommitted)

🍅读已提交(Read Committed)

🍅可重复读(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;-- 根据执行结果决定提交还是回滚事务-- 提交事务,使所有操作生效,并对更改持久化commit;-- 回滚当前事务,撤销所有操作,回到原来的状态rollback;
  • 开启事务后,所写的SQL语句就包含在事务中,都具有ACID特性;
  • 无论提交事务还是回滚事务,事务都会关闭;
  • commit之后也就是提交之后,事务就不能回滚了。

🍠事务的使用

🍐回滚的使用

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

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

-- 查询数据mysql> select* from account;+----+--------+-------+| 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 account set money=money-100 where name=\'小王\';Query OK, 1 row affected (0.01 sec)Rows matched: 1 Changed: 1 Warnings: 0mysql> update account set money=money+100 where name=\'小明\';Query OK, 1 row affected (0.00 sec)Rows matched: 1 Changed: 1 Warnings: 0-- 查询转账后的结果mysql> select* from account;+----+--------+-------+| 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 account;+----+--------+-------+| 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 account set money=money-100 where name=\'小王\';Query OK, 1 row affected (0.09 sec)Rows matched: 1 Changed: 1 Warnings: 0mysql> update account set money=money+100 where name=\'小明\';Query OK, 1 row affected (0.04 sec)Rows matched: 1 Changed: 1 Warnings: 0-- 提交事务mysql> commit;Query OK, 0 rows affected (0.00 sec)-- 提交事务后的数据mysql> select* from account;+----+--------+-------+| 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 account;+----+--------+-------+| 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:小王向小明转账100mysql> update account set money=money-100 where name=\'小王\';Query OK, 1 row affected (0.01 sec)Rows matched: 1 Changed: 1 Warnings: 0mysql> update account set money=money+100 where name=\'小明\';Query OK, 1 row affected (0.00 sec)Rows matched: 1 Changed: 1 Warnings: 0-- 查询转账后的结果mysql> select* from account;+----+--------+-------+| 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:小王再次向小明转账50mysql> update account set money=money-50 where name=\'小王\';Query OK, 1 row affected (0.00 sec)Rows matched: 1 Changed: 1 Warnings: 0mysql> update account set money=money+50 where name=\'小明\';Query OK, 1 row affected (0.00 sec)Rows matched: 1 Changed: 1 Warnings: 0-- 查询转账结果mysql> select* from account;+----+--------+-------+| 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 account;+----+--------+-------+| id | name | money |+----+--------+-------+| 1 | 小王 | 100 || 2 | 小明 | 300 |+----+--------+-------+2 rows in set (0.00 sec)-- 回滚保存点,事务不会关闭-- 再次尝试转账,回滚到保存点mysql> update account set money=money-50 where name=\'小王\';Query OK, 1 row affected (0.00 sec)Rows matched: 1 Changed: 1 Warnings: 0mysql> update account set money=money+50 where name=\'小明\';Query OK, 1 row affected (0.00 sec)Rows matched: 1 Changed: 1 Warnings: 0mysql> select* from account;+----+--------+-------+| 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 account;+----+--------+-------+| id | name | money |+----+--------+-------+| 1 | 小王 | 100 || 2 | 小明 | 300 |+----+--------+-------+2 rows in set (0.00 sec)mysql> update account set money=money-50 where name=\'小王\';Query OK, 1 row affected (0.00 sec)Rows matched: 1 Changed: 1 Warnings: 0mysql> update account 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 account;+----+--------+-------+| id | name | money |+----+--------+-------+| 1 | 小王 | 200 || 2 | 小明 | 200 |+----+--------+-------+2 rows in set (0.00 sec)
  • 在同一事务中,保存点名称是唯一的。如果设置相同名称的保存点名称,后设置的保存点会覆盖先设置的;
-- 开启事务mysql> begin;Query OK, 0 rows affected (0.00 sec)-- 转账100mysql> update account set money=money-100 where name=\'小王\';Query OK, 1 row affected (0.01 sec)Rows matched: 1 Changed: 1 Warnings: 0mysql> update account set money=money+100 where name=\'小明\';Query OK, 1 row affected (0.00 sec)Rows matched: 1 Changed: 1 Warnings: 0-- 查询结果mysql> select* from account;+----+--------+-------+| 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)-- 转账50mysql> update account set money=money-50 where name=\'小王\';Query OK, 1 row affected (0.00 sec)Rows matched: 1 Changed: 1 Warnings: 0mysql> update account set money=money+50 where name=\'小明\';Query OK, 1 row affected (0.00 sec)Rows matched: 1 Changed: 1 Warnings: 0-- 查询结果mysql> select* from account;+----+--------+-------+| 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)-- 转账25mysql> update account set money=money-25 where name=\'小王\';Query OK, 1 row affected (0.00 sec)Rows matched: 1 Changed: 1 Warnings: 0mysql> update account set money=money+25 where name=\'小明\';Query OK, 1 row affected (0.00 sec)Rows matched: 1 Changed: 1 Warnings: 0mysql> select* from account;+----+--------+-------+| id | name | money |+----+--------+-------+| 1 | 小王 | 25 || 2 | 小明 | 375 |+----+--------+-------+2 rows in set (0.00 sec)-- 回滚到保存点money1mysql> rollback to savepoint money1;Query OK, 0 rows affected (0.00 sec)-- 回滚到后设置的保存点位置mysql> select* from account;+----+--------+-------+| id | name | money |+----+--------+-------+| 1 | 小王 | 50 || 2 | 小明 | 350 |+----+--------+-------+2 rows in set (0.00 sec)
  • 回滚保存点不会关闭事务。

 🍠自动/手动提交事务

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

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

语法

show variables like \'autocommit\';
  • autocommmit:系统变量,标识事务是否自动提交。 

示例: 

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

设置自动

-- 方式一:set autocommit=1;-- 方式二:set autocommit=ON;

设置手动

方式一:set autocommit=0;方式二:set autocommit=off;

示例:设置手动

mysql> set autocommit=0;Query OK, 0 rows affected (0.00 sec)mysql> set autocommit=OFF;Query OK, 0 rows affected (0.00 sec)mysql> show variables like \'autocommit\';+---------------+-------+| Variable_name | Value |+---------------+-------+| autocommit | OFF |+---------------+-------+1 row in set, 1 warning (0.00 sec)
🍎手动的使用
-- 设置手动:mysql> set autocommit=0;Query OK, 0 rows affected (0.00 sec)-- 不用显示开启事务-- 转账mysql> update account set money=money-100 where name=\'小王\';Query OK, 1 row affected (0.01 sec)Rows matched: 1 Changed: 1 Warnings: 0mysql> update account set money=money+100 where name=\'小明\';Query OK, 1 row affected (0.00 sec)Rows matched: 1 Changed: 1 Warnings: 0查询mysql> select* from account;+----+--------+-------+| 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 account;+----+--------+-------+| id | name | money |+----+--------+-------+| 1 | 小王 | 200 || 2 | 小明 | 200 |+----+--------+-------+2 rows in set (0.00 sec)
🍎手动和自动的区别:
  • 自动要开启事务(start transaction/begin),通过commit或rollback结束事务 
  • 手动不用显示开启事务,通过commit或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 uncommitted;Query OK, 0 rows affected (0.00 sec)-- 方式二mysql> set transaction_isolation = \'read-uncommitted\';Query OK, 0 rows affected (0.00 sec)-- 方式三mysql> set @@session.transaction_isolation = \'read-uncommitted\';Query OK, 0 rows affected (0.00 sec)
  • 后面两种方式遇到空格要用“-”代替; 
  • access_mode(访问模式):只读(read only)、读写(read write,默认)
  • 隔离级别分明四种:读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)、串行化(Serializable)
🍊隔离级别
🍅读未提交(Read Uncommitted)

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

🍅读已提交(Read Committed)

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

🍅可重复读(Repeatable Read)

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

🍅串行化(Serializable)

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

  •  在安全性方面:读未提交<读已提交<可重复读<串行化。
  • 在并发性能方面:读未提交>读已提交>可重复读>串行化。并发性能是指数据库在同一时刻能够处理多个并发事务的能力。