MySQL事务的ACID特性以及并发问题

专栏持续更新中:MySQL详解

一、事务概念

InnoDB支持事务,而MyISAM不支持事务

一个事务是由一条或者多条对数据库操作的SQL语句所组成的一个不可分割的单元,只有当事务中的所有操作都正常执行完了,整个事
务才会被提交给数据库
;如果有部分事务处理失败,那么事务就要回退到最初的状态,因此,事务要么全部执行成功,要么全部失败。

所以记住事务的几个基本概念,如下:

  1. 事务是一组SQL语句的执行,要么全部成功,要么全部失败,不能出现部分成功,部分失败的结果。保证事务执行的原子性
  2. 事务的所有SQL语句全部执行成功,才能提交(commit) 事务,把结果写回磁盘
  3. 事务执行过程中,有SQL出现错误,那么事务必须要回滚(rollback) 到最初的状态

比如转账业务需要多条SQL语句共同完成,只有这些SQL都执行成功才算业务成功了

begin开启事务,如果这2句SQL都成功了,那么commit提交一个事务

如果其中任意一条SQL由于停电,或者服务器出错,导致SQL执行异常,那事务就没有提交,事务会回滚,数据将恢复到事务开始前的状态

这是存储引擎来保证的(redo log和undo log保证的)

查看当前数据库支持的存储引擎

ini 复制代码
show engines;

数据库引擎可以通过命令临时修改,或者通过配置文件永久修改

scss 复制代码
select @@autocommit;

@autocommit为1表示事务自动提交,为0表示事务手动提交

做业务的时候,我们一般会在代码上控制这个变量,一般来说,我们的事务由多条SQL组成,所以我们设置为手动提交。业务都成功,则提交这个事务;如果业务中间出现失败,就回滚1个事务

二、ACID特性

每一个事务必须满足下面的4个特性:

  • 原子性(Atomic):事务是一个不可分割的整体,事务必须具有原子特性。当修改数据时,要么全执行,要么全不执行,即不允许部分事务完成
  • 一致性(Consistency):事务执行之前和执行之后,数据库数据必须保持一致性状态。数据库的一致性状态必须由用户来负责,由并发控制机制实现。拿银行转账来说,只有让一个用户的余额减少,又让一个用户的余额增加才能构成一个完整的事务
  • 隔离性(Isolation):当两个或者多个事务并发执行时,为了保证数据的安全性,将一个事物内部的操作与其它事务的操作隔离起来,不被其它正在执行的事务所看到,使得并发执行的各个事务之间不能互相影响。隔离级别:数据的安全性和事务的并发性。隔离越严格,安全性越高,并发性越低(就是并发控制,保证数据安全)
  • 持久性(Durability):事务完成(事务commit成功)以后,DBMS保证它对数据库中的数据的修改是永久性的,即使数据库因为故障出错,也应该能够恢复数据

InnoDB 引擎通过什么技术来保证事务的这四个特性的呢?

  • 持久性是通过 redo log (重做日志)来保证的
  • 原子性是通过 undo log(回滚日志) 来保证的
  • 隔离性是通过 MVCC(多版本并发控制) 或锁机制来保证的
  • 一致性则是通过持久性+原子性+隔离性来保证

DB写数据都是先在cache缓存上写的(因为速度快),然后操作系统通过磁盘I/O往磁盘上写,当事务成功提交后,commit就返回了。然后cache再慢慢往磁盘上写数据,这个过程中如果由于不可抗因素中断了,导致缓存上的数据向磁盘上写的时候没写完,那此时数据就丢了。系统重启后MySQL数据库会根据redo log来重新执行这个事务并写入缓存,然后写入磁盘,来保证数据库的持久性。由于用户会写很多数据,所以commit不会等着这些数据从缓存全部写到磁盘再返回,因为要经过磁盘I/O,业务上不可能让用户去等那么长时间

MySQL最重要的是日志,不是数据!

事务的ACD特性由redo log和undo log机制保证,事务的I特性由事务的锁机制来保证,锁粒度越大,事务隔离性越好,安全性越高,并发性越低,效率越低

三、事务并发存在的问题

事务处理如果不经隔离,并发执行事务时通常会发生以下问题:

  • 脏读(Dirty Read):一个事务读取了另一个事务未commit的数据(处理了一半的数据) 。例如当事务A和事务B并发执行时,当事务A更新后,事务B查询读取到A尚未提交的数据,此时如果事务A rollback了,那事务B读到的数据就不是数据库所存放的数- 据了,而是无效的脏数据(脏读必须杜绝,因为事务没有commit;而不可重复读和幻读不一定出问题,因为事务已经commit
  • 不可重复读(NonRepeatable Read):一个事务的操作导致另一个事务前后两次读取到不同的数据 。例如当事务A和事务B并发执行时,当事务B查询读取数据后,事务A update操作更改事务B查询到的数据,此时事务B再次去读该数据,发现前后两次读的数据不一样(事务B读取了事务A已commit的数据
  • 幻读(Phantom Read):一个事务的操作导致另一个事务前后两次查询的结果数据量不同 。例如 当事务A和事务B并发执行时,当事务B查询读取数据后,事务A新增或者删除了一条满足事务B查询条件的记录,此时事务B再去查询,发现查询到前一次不存在的记录,或者前一次查询的一些记录不见了(事务B读取了事务A新增加的数据或者读不到事务A删除的数据

在有些场景下,不可重复读和幻读一定程度上是可以允许的,不一定非要杜绝(通过设置不同的隔离级别解决),由应用场景需求决定

  • 脏读举例:张三的账户还剩100块,他同时发起两个事务,事务A转账50,事务B购买价格为80的水杯,事务A现对张三的余额减50,然后给另一个人余额加50。由于没有隔离控制,事务B购买水杯前先读取余额,发现只有50,无法购买茶杯,于是茶杯购买失败。此时事务A执行异常,将张三的余额回滚为事务执行前的状态,余额为100。当前场景中事务B读到了事务A还没有commit的数据50,发生了脏读,任何业务场景下都必须杜绝
  • 不可重复读举例:首先事务B查询余额,发现为100。事务A完成转账50,并且commit,事务B再次查询余额,发现变成了50,在某些业务场景下是可以允许的,不一定非要杜绝
  • 幻读举例:事务B查询年龄为20的人,发现有5个。事务A插入或删除了年龄为20的记录,并且commit,事务B再次查询年龄为20的人,发现已经不是5个人了,幻读也是在某些业务场景下是可以允许的,不一定非要杜绝

四、事务相关命令

查看MySQL是否自动提交事务

1表示自动提交事务,0表示手动提交事务

一般我们业务上如果要考虑到事务处理,我们需要设置为手动提交方式,如果一个事务包含多个SQL,若是自动提交方式,一句SQL执行完就自动提交了,后面的SQL万一执行失败就无法正常rollback,无法保证事务的原子特性

  • BEGIN:开启一个事务
  • COMMIT:提交一个事务
  • ROLLBACK:回滚一个事务到初始的位置
  • SAVEPOINT point1:设置一个名字为point1的保存点
  • ROLLBACK TO point1:事务只回滚到保存点point1,而不是回滚到初始状态
  • SET TRANSACTION_ISOLATION='REPEATABLE-READ':设置事务的隔离级别
  • SELECT @@TRANSACTION_ISOLATION:查询事务的隔离级别
相关推荐
数据知道11 分钟前
PostgreSQL 核心原理:如何利用多核 CPU 加速大数据量扫描(并行查询)
数据库·postgresql
爬山算法13 分钟前
Hibernate(90)如何在故障注入测试中使用Hibernate?
java·后端·hibernate
Moment31 分钟前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
Cobyte1 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc
麦聪聊数据1 小时前
Web 原生架构如何重塑企业级数据库协作流?
数据库·sql·低代码·架构
未来之窗软件服务1 小时前
数据库优化提速(四)新加坡房产系统开发数据库表结构—仙盟创梦IDE
数据库·数据库优化·计算机软考
程序员侠客行2 小时前
Mybatis连接池实现及池化模式
java·后端·架构·mybatis
Honmaple2 小时前
QMD (Quarto Markdown) 搭建与使用指南
后端
PP东3 小时前
Flowable学习(二)——Flowable概念学习
java·后端·学习·flowable
invicinble3 小时前
springboot的核心实现机制原理
java·spring boot·后端