mysql的事务详解(代码片段)

荣· 荣·     2022-12-23     657

关键词:

博客主页:系列专栏: MySQL
一句短话: 难在坚持,贵在坚持,成在坚持!

文章目录

一. 事务的业务场景

在数据库中 事务(transaction) 可以把多个SQL给打包到一起, 即将多个SQL语句变成一个整体, 也就是说一个事务中的所有操作要么全部成功执行, 要么完全不执行.

通过实际场景来理解事务:

实际生活中我们经常涉及转帐操作, 张三给李四转账2000元, 涉及到两个操作

  1. 给张三的账户余额减去2000元
  2. 给李四的账户余额增加2000元

这里就要考虑到这两个操作的完整性, 也就是不能出现张三的账户余额减少了2000元, 但李四的账户余额未发生变化, 这就要求上面的两个操作要么全部执行完成功转账, 要么一个都不执行双方都没有损失, 不会出现中途发生一些问题导致数据不一致的情况.

这样的一次完整操作叫做 事务(transaction), 一个事务中的所有操作要么全部成功执行, 要么完全不执行.

二. 事务的使用

事务是如何保证操作的完整性的呢?

其实事务执行中间出错了, 只需要让事务中的这些操作恢复成之前的样子即可, 这里涉及到的一个操作, 回滚(rollback).

事务处理是一种对必须整批执行的 MySQL 操作的管理机制, 在事务过程中, 除非整批操作全部正确执行, 否则中间的任何一个操作出错, 都会回滚 (rollback)到最初的安全状态以确保不会对系统数据造成错误的改动.

相关语法:

-- 开启事务
start transaction;

-- 若干条执行sql

-- 提交/回滚事务
commit/rollback;

注意:

在开启事务之后, 执行sql不会立即去执行, 只有等到commit操作后才会统一执行(保证原子性).

示例:
首先创建一个账户表并初始化数据

-- 创建一个账户表
create table account(
     id int primary key auto_increment,
	 name varchar(20),
	 money double(10,2)
);
-- 初始化账户信息
insert into account(name, money) values ('张三', 10000), ('李四', 10000);

首先看正常情况下的转账操作

-- 张三账户 -2000
mysql> update account set money = money - 2000 where name = '张三';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0
-- 李四账户 +2000
mysql> update account set money = money + 2000 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 | 张三   |  8000.00 |
|  2 | 李四   | 12000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

如果操作中出现异常情况, 比如sql语句中所写的注释格式错误导致sql执行中断.

-- 先将张三和李四的账户余额恢复为10000元
update account set money = 10000 where name = '张三';
update account set money = 10000 where name = '李四';
-- 张三 -2000
mysql> update account set money = money - 2000 where name = '张三';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0
-- 李四 +2000
mysql> 没加--的注释
    -> update account set money = money + 2000 where name = '李四';
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '没加--的注释
update account set money = money + 2000 where name = '李四'' at line 1
-- 出现异常
mysql> select * from account;
+----+--------+----------+
| id | name   | money    |
+----+--------+----------+
|  1 | 张三   |  8000.00 |
|  2 | 李四   | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

观察结果发现了张三的账户少了2000元, 但李四的账户余额并没有增加, 在实际操作中这种涉及钱的操作发生这种失误可能会造成很大的损失.

为了防止这种失误的出现我们就可以使用事务来打包这些操作.

-- 先将张的账户余额恢复为10000元
update account set money = 10000 where name = '张三';
-- 开启事务
start transaction;
-- 张三 -2000
mysql> update account set money = money - 2000 where name = '张三';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
-- 李四 -2000
mysql> 没加--的注释
    -> update account set money = money + 2000 where name = '李四';
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '没加--的注释
update account set money = money + 2000 where name = '李四'' at line 1
-- 预期结果
mysql> select * from account;
+----+--------+----------+
| id | name   | money    |
+----+--------+----------+
|  1 | 张三   |  8000.00 |
|  2 | 李四   | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

观察这里的结果发现在当前的数据库用户查询到的account表中的账户余额发生了变化,但开启了事务之后在commit之前只是临时的预操作并不会真的去修改表中的数据;

可以退出数据库再打开重新查询表中数据或者切换用户去查询去验证表中数据是否发生改变, 这里就不作演示了.

发现操作结果异常之后, 当前用户需要恢复到事务之前的状态, 即进行回滚操作.

-- 回滚事务
mysql> rollback;
Query OK, 0 rows affected (0.03 sec)
-- 验证回滚后的状态
mysql> select * from account;
+----+--------+----------+
| id | name   | money    |
+----+--------+----------+
|  1 | 张三   | 10000.00 |
|  2 | 李四   | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

如果开启事务之后发现预操作的结果是预期的效果, 此时我们就可以提交事务, 当我们提交完事务之后, 数据就是真的修改了, 也就是硬盘中存储的数据真的改变了.

-- 开启事务
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
-- 张三 -2000
mysql> update account set money = money - 2000 where name = '张三';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
-- 李四 +2000
mysql> update account set money = money + 2000 where name = '李四';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
-- 提交事务
mysql> commit;
Query OK, 0 rows affected (0.03 sec)

mysql> select * from account;
+----+--------+----------+
| id | name   | money    |
+----+--------+----------+
|  1 | 张三   |  8000.00 |
|  2 | 李四   | 12000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)

要注意事务也不是万能的, 不能保证你删表删库之后可以完全恢复, 只是在适量的数据和操作下使用事务可以避免一些问题.

回滚(rollback)操作, 实际上是我们把事务中的操作再进行逆操作, 前面是插入, 回滚就是删除…

这些操作是有很大开销的, 可以保存, 但不能够无限保存, 最多是将正再执行的事务保存下来, 额外的内容就不好再保存了; 数据库要是有几十亿条数据, 占据了几百G硬盘空间, 不可能去花费几个T甚至更多的空间用来记录这些数据是如何来的.

三. 事务的特性(ACID)

1. 原子性(Atomicity)

一个事务是一个不可分割的最小单位, 事务中的所有操作要么全部成功, 要么全部失败, 没有中间状态.

原子性主要是通过事务日志中的回滚日志(undo log)来实现的, 当事务对数据库进行修改时, InnoDB 会根据操作生成相反操作的 undo log, 比如说对 insert 操作, 会生成 delete 记录, 如果事务执行失败或者调用了 rollback,就会根据 undo log 的内容恢复到执行之前的状态.

事务的原子性, 也是事务的核心特性, 是事务的初心.

2. 一致性(Consistency)

事务执行之前和执行之后数据都是合法的一致性状态, 即使发生了异常, 也不会因为异常引而破坏数据库的完整性约束, 比如唯一性约束等.

事务执行前/执行后, 都得是数据合法的状态; 比如像上面的转账, 不能说转的过程出错了, 导致出现钱转丢了的情况.

3. 持久性(Durability)

事务提交之后对数据的修改是持久性的, 即使数据库宕机也不会丢失, 通过事务日志中的重做日志(redo log)来保证; 事务修改之前, 会先把变更信息预写到 redo log 中, 如果数据库宕机, 恢复后会读取 redo log 中的记录来恢复数据(回滚).

事务产生的修改, 都是会写入硬盘的, 程序重启/主机重启/掉电, 事务都可以正常工作, 保证修改是生效的.

4. 隔离性(Isolation)

这里的隔离性是指一个数据库服务器, 同时执行多个事务的时候, 事务之间的相互影响程度.

一个服务器, 可以同时给多个客户端提供服务, 这多个客户端是并发执行的关系, 多个客户端就会有多个事务, 多个事务同时去操作一个表的时候, 特别容易出现互相影响的问题.

如果隔离性越高, 就意味着事务之间的并发程度越低, 执行效率越慢, 但是数据准确性越高.

如果隔离性越低, 就意味着事务之间的并发程度越高, 执行效率越快, 但是数据准确性越低.

隔离性通过事务的隔离级别来定义, 并用锁机制来保证写操作的隔离性, 用 MVCC 来保证读操作的隔离性.

四. 事务并发异常

在实际生产环境下, 可能会出现大规模并发请求的情况, 如果没有妥善的设置事务的隔离级别, 就可能导致一些异常情况的出现,最常见的几种异常为脏读(Dirty Read), 幻读(Phantom Read)不可重复读(Unrepeatable Read).

1. 脏读

一个事务读取到了另外一个事务没有提交的数据(读写的是同一份数据).

说详细点就是当一个事务正在访问数据并且对数据进行了修改, 而这种修改还没有提交到数据库中,与此同时时另外一个事务也访问这个数据, 然后使用了这个数据; 因为这个数据是还没有提交的数据, 那么另外一个事务读到的这个数据就是脏数据, 依据脏数据所做的操作可能是不正确的.

用一个场景例子来理解, 张三正在写代码, 李四趴在屏幕前看张三写代码, 等张三走掉之后, 李四就把他刚刚写的这段代码删掉了, 此时李四看到的这段代码就可能是一个错误的代码.

在这个场景下, 张三和李四就可以理解为两个事务, 这两个事务是完全并发没有任何限制的, 此时就会出现脏读问题.

解决脏读问题的办法, 就是降低并发性, 提高隔离性, 具体来说就是给这里的 “写操作” 加锁, 张三在写代码的时候, 李四不能看, 张三和李四约定张三代码写完后会提交到githup上, 李四去githup上去看.

当进行了写加锁的时候, 张三写的时候, 李四就不能同时去读了; 相当于降低了并发程度, 提高了隔离性. 降低了一定的效率, 但是提高了准确性.

2. 不可重复读

在同一事务中, 连续两次读取同一数据, 得到的结果不一致.

还是基于上面的场景进行理解, 上面已经约定了写加锁(张三写代码过程中, 李四不要读, 等到张三提交之后, 李四再去读).

此时张三在写代码, 张三和李四有约定, 所以此时李四在等张三把代码提交到githup上再去看代码.

过了一会儿, 张三写完了, 并将代码提交到了githup上, 李四开始读代码.

当李四正在读这个代码的时候, 张三觉得自己的代码还有不足, 于是张三动手修改, 重新提交了个版本; 导致李四读代码读了一半, 突然代码自动就变了.

这种情况就是不可重复读问题了, 解决办法是给读操作也加锁, 张三在读代码的时候, 李四不能修改.

此时这两个事务之间的并发程度进一步降低了, 隔离性又进一步提高了, 运行速度又进一步变慢了, 数据的准确性又进—步提高了.

3. 幻读

同一事务中, 用同样的操作读取两次, 得到的记录数不相同.

幻读是指当事务不是独立执行时发生的一种现象, 例如第一个事务对一个表中的数据进行了修改, 这种修改涉及到表中的全部数据行; 同时, 第二个事务也修改这个表中的数据, 这种修改是向表中插入一行新数据; 那么, 以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行, 就好象发生了幻觉一样.

再基于2中的场景进行理解, 当前已经约定了写加锁和读加锁, 即张三写代码的时候, 李四不能读; 李四读代码的时候, 张三也不能写.

此时李四在读代码, 张三虽然不能去修改李四现在正在读的这个文件, 但是李四又去新增/删除一个其他的文件, 此时, 虽然李四读的代码内容没变, 但他发现, 文件的数量变了; 这就是幻读问题了.

解决幻读问题的办法是 串行化, 也就是彻底的舍弃并发, 此时只要李四在读代码, 张三就不能进行任何操作.

四. MySQL的四个隔离级别

MySQL中有 4 种事务隔离级别, 由低到高依次为 读未提交 Read Uncommitted, 读已提交 Read Committed , 可重复读 Repeatable Read , 串行化 Serializable.

串行化的事务处理方式是最安全的, 但不能说用这个就一定好, 应该是根据实际需求去选择合适的隔离级别, 比如银行等涉及钱的场景, 就需要确保准确性, 速度慢一点也没什么; 而比如抖音,B站,快手等上面的点赞数, 收藏数就没必要那么精确了, 这个场景下速度提高一点体验会更好一些.

脏读不可重复读幻读
读未提交 read uncommited
读已提交 read commited
可重复读 repeatable read
串行化 serializable
  1. read uncommited

不做任何限制, 事务之间都是随意并发执行的; 并发程度最高,隔离性最差.

会产生脏读 + 不可重复读 + 幻读问题.

  1. read commited

对写操作加锁, 并发程度降低, 隔离性提高.

解决了脏读问题, 仍然存在不可重复读 + 幻读问题.

  1. repeatable read

写加锁, 读加锁, 隔离性再次提高, 并发程度再次降低.

解决了脏读 + 不可重复读问题, 仍然存在幻读问题.

这个隔离级别也是MySQL的默认隔离级别, 如果需要改的话, 可以通过MySQL的配置文件来进行调整.

  1. serializable

严格执行串行化, 并发程度最低, 隔离性最高, 执行速度最慢.

解决了 脏读 + 不可重复读 + 幻读问题.

mysql5事务详解(代码片段)

一概念事务(Transaction)是访问和更新数据库的程序执行单元;事务中可能包含一个或多个sql语句,这些语句要么都执行,要么都不执行。作为一个关系型数据库,MySQL支持事务,本文介绍基于MySQL5.6。 一个经典案例说明事务... 查看详情

mysql中事务以及事务隔离级别的详解(代码片段)

...分享一下给大家,👉点击跳转到网站一、MySQL中事务的理解二、演示事务的基本使用DROPTABLEt27;--1.创建一张测试表CREATETABLEt27(idINT,`name`VARCHAR(32));--2.开启事务STARTTRA 查看详情

mysql系列详解四:mysql事务-技术流ken(代码片段)

MySQL事务MySQL事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你即需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成... 查看详情

mysql事务详解(代码片段)

🏆今日学习目标:🍀Spring事务和MySQL事务详解✅创作者:林在闪闪发光⏰预计时间:30分钟🎉个人主页:林在闪闪发光的个人主页 🍁林在闪闪发光的个人社区,欢迎你的加入: 林在闪闪发光... 查看详情

mysql事务详解(代码片段)

前言要么全都带走,要么一无所有。----卡牌大师崔斯特在实际业务场景中,如何保证操作的完整性是一个重要的议题,依次执行一系列逻辑强关联的操作,如果在中途发生了错误,就很有可能导致数据的错乱... 查看详情

mysql事务-rollback,commit用法详解(代码片段)

使用ROLLBACK既然我们已经知道了什么是事务处理,下面讨论事务处理的管理中所涉及的问题。管理事务处理的关键在于将SQL语句组分解为逻辑块,并明确规定数据何时应该回退,何时不应该回退。MySQL使用下面的语句来标识事务... 查看详情

mysql事务——万字详解(代码片段)

目录前言一.事务的概念    1.1什么是事务    1.2事务的属性    1.3事务的版本支持     1.4事务提交方式     1.5事务的常见操作    1.5.1准备阶段:     1.5.2手动演示回滚操作     1.5.3简单证明原子性     1.... 查看详情

netcore中数据库事务隔离详解——以dapper和mysql为例(代码片段)

原文:NetCore中数据库事务隔离详解——以Dapper和Mysql为例NetCore中数据库事务隔离详解——以Dapper和Mysql为例事务隔离级别准备工作Readuncommitted读未提交Readcommitted读取提交内容Repeatableread(可重读)Serializable序列化总结事务隔离级别.NE... 查看详情

mysql进阶mysql事务详解(代码片段)

...详解8【MySQL基础】MySQL多表操作详解文章目录前言1,事务概念2,事务四大特性2.1,原子性2.2,一致性2.3,隔离性2.4,持久性3,事务基本操作3.1,开启事务3.2,回滚事务3.3,提交事务3.4,... 查看详情

mysql事务(详解)(代码片段)

Mysql事务一、事务存在的意义1、事务定义事务:事务是一个最小的不可在分的工作单元;通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务是一个最小的工作单元)一个完整的业务需要批量的DML(insert、... 查看详情

mysql事务详解(代码片段)

...器故障导致数据不一致的情况。这样的一次完整操作叫做事务transaction,一个事务中的所有操作要么全部成功执行,要么完全不执行。本文将会介绍MySQL的事务ACID特性和MySQL事务控制流程的语法,并介绍事务并发处理... 查看详情

mysql索引与事务详解(代码片段)

MySQL索引与事务详解一、前言?上一章我们讲解了MySQL的手工编译安装流程以及相关的数据库操作命令(sql语句),本文将要详细介绍MySQL索引与事务的概念及原理,并初步了解MySQL数据库视图概念,简述数据库的存储过程。二、索... 查看详情

mysql数据库的核心mvcc详解(代码片段)

...  在看MVCC之前我们先补充些基础内容,首先来看下事务的ACID。2.MySQL的核心日志  在MySQL数据库中有三个非常重要的日志binlog,undolog,redolog.  举例演示为:3.隔离级别  MySQL的事务隔离级别有四个,分别为:... 查看详情

mysql事务隔离级别详解(代码片段)

一、什么是事务?事务是逻辑上的一组操作,要么全执行,要么全不执行。事务最经典栗子也经常被拿出来的栗子就是银行转账了。比如小明要给小红转账1000元,这个转账会涉及到两个关键操作:将小明的余... 查看详情

mysql状态变量详解(代码片段)

...日志缓存但超过binlog_cache_size值并使用临时文件存储来自事务的语句的事务数 Binlog_cache_use0 使用二进制日志缓存的事务数Binlog_stmt_cache_disk_use0 使用二进制日志 查看详情

mysql数据库的高级使用事务&索引(超详解)(代码片段)

...表中的品牌信息4.小结修改goods表结构修改goods表结构小结事务1.事务的介绍2.事务的四大特性3.事务的使用4.小结索引1.索引的介绍2.索引的使用3.案例-验证索引查询性能4.联合索引5.联合索引的最左原则6.MySQL中索引的优点和缺点和... 查看详情

mysql8.0新增特性详解(代码片段)

...以看出,大概每1~3个月一个版本。2.MySQL8.0中新增的特性事务性数据字典数据字典表以InnoDB表存储字典数据,位于mysql数据库下,对外不可见。有专门的表空间mysql.idb,位于数据目录下。但是可以通过informaction_schema下面的一些表... 查看详情

mysqlbinlog日志操作详解(代码片段)

...,还包含语句所执行的消耗的时间,MySQL的二进制日志是事务安全型的。binlog日志的作用其一:MySQLReplication在Master端开启binlog,Mster把它的二进制日志传递给slaves来达到master-slave数据 查看详情