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

2023-11-08 09:05:46 浏览数 (1)

专栏持续更新中:MySQL详解

一、事务概念

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

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

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

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

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

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

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

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

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

代码语言:javascript复制
show engines;

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

代码语言:javascript复制
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:查询事务的隔离级别

0 人点赞