MySQL事务及ACID四大特性详解

后端 潘老师 5个月前 (11-21) 112 ℃ (0) 扫码查看

本文主要讲解关于MySQL事务及ACID四大特性详解相关内容,让我们来一起学习下吧!

事务概念

事务(Transaction)是一个操作序列,这些操作要么都做,要么都不做,是一个不可分割的工作单位。

START TRANSACTION;
INSERT INTO users (id, name) values (1, "王小明");
INSERT INTO users (id, name) values (2, "李小狼");
COMMIT;

事务以BEGIN TRANSACTION开始,以COMMIT或者ROLLBACK结束,COMMIT表示提交,将未存储的SQL语句的结果写入数据库ROLLBACK表示回滚,撤销SQL语句的执行。

在Go代码中可以用gorm来使用事务:

func (dao *UserDAO) InsertUsers(c context.Context) {
    db := dao.db
    db.Transaction(func(tx *gorm.DB) error {
        if err := tx.Create(&User{ID: 1, Name: "王小明"}).Error; err != nil {
            // 返回任何错误都会回滚事务
            return err
        }
        if err := tx.Create(&User{ID: 2, Name: "李小狼"}).Error; err != nil {
            return err
        }
        // 返回nil提交事务
        return nil
    })
}

事务有4个特性,一般称为ACID特性:

  • 原子性(Atomicity)事务在逻辑上是不可分割的操作单元,所有语句要么都执行,要么都不执行。
  • 一致性(Consistency)一个事务就是一系列在逻辑上相关的操作的指令的集合,用于完成一项任务,其本质是将数据库中的数据从一种一致性状态转换到另一种一致性状态,以体现现实世界中的状态变化。
  • 隔离性(Isolation)隔离性是针对并发事务而言的,并发执行的各个事务之间不能互相干扰(并发是指数据库服务器同时处理多个事务)。如果不采取专门的控制机制,那么并发事务之间可能会相互干扰,进而导致数据出现不一致或错误的状态。
  • 持久性(Durability)持久性是指一旦事务提交成功,其对数据的修改是持久性的。数据更新的结果已经从内存转存到了外部存储器上,此后即使发生了系统故障,已提交事务所做的数据更新也不会丢失。

MySQL的日志有很多种,比如二进制日志、错误日志、查询日志、慢查询日志等,此外InnoDB还提供了两种事务日志:redo log(重做日志)和 undo log(回滚日志)。其中redo log用于保证事务持久性;undo log是事务原子性和隔离性实现的基础。下文会简单地说一下回滚日志和重做日志大致是怎样来保证这些事务的特性的,注意这里是大致说明,日志背后的数据结构和实现包括刷盘方式等,我目前还没有进行深入了解。

原子性

原子性指事务在逻辑上是不可分割的操作单元,所有语句要么都执行,要么都不执行。

当事务中的SQL执行失败,或者需要回滚的时候,使用undo log(回滚日志),当事务对数据库进行修改时,InnoDB会生成对应的undo log,如果任务执行失败或者调用了ROLLBACK,可以利用undo log中的信息将数据回滚到修改之前的样子。

undo log日志中存储的是逻辑日志,包含了操作类型(insert或update)和数据信息等。根据undo log中的数据进行操作时,执行的是相反的操作,比如insert对应的是delete,delete对应的是insert,update对应的是一个相反的update。

执行下面的事务时:

START TRANSACTION;
-- 语句1
INSERT INTO users (id, name) values (1, "王小明");
-- 语句2
UPDATE users SET name="李小狼" WHERE id=1;
-- 语句3
SELECT * FROM users;
COMMIT;

执行语句1后,生成了一条undo log 1,这个log记录了语句1执行的操作(insert)。

执行语句2后,生成了一条undo log 2,这个log记录了语句2执行的操作(update),以及操作执行之前的数据(”王小明”)。

语句3是查询操作,没有undo log。

假如语句1和语句2都执行成功了,此时数据库中的数据是:

+----+-----------+
| id | name      |
+----+-----------+
|  1 | 李小狼     |
+----+-----------+

当执行语句3出现异常后,通过undo log日志对事务进行回滚时,可以大致理解为,执行以下语句回滚语句2:

UPDATE users SET name="王小明" WHERE id=1;

执行以下语句回滚语句1:

DELETE FROM users WHERE id=1;

这样数据库中的数据就回滚到了事务执行之前。保证了事务的原子性,要么都执行,要么都不执行。

持久性

持久性是指一旦事务提交成功,其对数据的修改是持久性的。**数据更新的结果已经从内存转存到了外部存储器上,此后即使发生了系统故障,已提交事务所做的数据更新也不会丢失。

为了减少磁盘IO,MySQL的InnoDB存储引擎提供了缓存池(Buffer Pool):

​ 当从数据库读取数据时,首先从Buffer Pool读取,如果Buffer Pool中没有,就从磁盘读取后放入Buffer Pool;

​ 当向数据库写入数据时,会首先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这一过程称为脏刷)。

如果在Buffer Pool中修改的数据还没写到磁盘时,MySQL服务宕机了,就会造成数据丢失,事务的持久性无法保证:事务提交成功了,但是对数据的修改丢失了。所以需要用redo log来保证持久性。

redo log存储的是物理日志,是基于磁盘的数据页的,和undo log存储的逻辑日志不同,简单来说逻辑日志会存储变化的过程,而物理日志只存储最终的结果。

执行下面的事务时:

START TRANSACTION;
-- 语句1
INSERT INTO users (id, name) values (1, "王小明");
-- 语句2
UPDATE users SET name="李小狼" WHERE id=1;
-- 语句3
SELECT * FROM users;
COMMIT;

假如这个事务提交成功,但是在Buffer Pool的数据还没有写到磁盘的时候,MySQL服务宕机了,当服务恢复之后,根据redo log日志的内容,磁盘中的数据应该是李小狼,所以会将磁盘中的数据存储为李小狼。

大致可以理解为执行以下语句重做事务:

INSERT INTO users (id, name) values (1, "李小狼");

这样保证了在事务提交成功和数据成功写入磁盘之间出现异常时,磁盘中仍然能保存下对数据的修改,保证了事务的持久性。

锁机制

锁可以分为表锁、行锁以及其他位于二者之间的锁。表锁在操作数据时会锁定整张表,行锁只锁定需要操作的行,间隙锁锁定行与行之间的某个间隙。

共享锁(Shared Lock),又称为读锁或S锁,它允许多个事务同时获取锁并读取同一份数据;排他锁(Exclusive Lock),又称为写锁,独占锁或者X锁,它只允许一个事务获取并持有该锁。

当事务想要获取一张表中的某几行的行级共享锁(S锁)时,MySQL会先自动获取该表的意向共享锁(IS锁);当事务想要获取一张表中某几行的行级排他锁(X锁)时,MySQL会自动获取该表的意向排他锁(IX锁)。

执行以下两个事务,但是两个事务都不提交,相当于两个事务都在执行中:

START TRANSACTION;
UPDATE users SET name = "王小明-1" WHERE id = 1;
-- COMMIT;

START TRANSACTION;
UPDATE users SET name = "李小狼-1" WHERE id = 2;
-- COMMIT;

然后执行以下语句查看加锁的情况:

SELECT * FROM performance_schema.data_locks;

图中的内容说明,加上了表级别的意向排他锁(因为需要对表中对两行数据加上排他锁),加上了行级别的X锁,锁覆盖的范围是2行数据。

  • LOCK_TYPE:TABLE表示是表级锁,RECORD表示是行级锁。
  • LOCK_MODE:IS、IX、S、X、S,GAP、X,GAP、S,REC_NOT_GAP、X,REC_NOT_GAP
  • LOCK_STATUS: GRANTED表示已添加,WAITING表示等待中。
  • LOCK_DATA:锁覆盖的范围。

当在事务中修改某行数据A时,是会对要修改的行进行加锁处理的,其他事务无法在事务执行期间读写数据A,保证了事务间对数据A的处理不会相互干扰。

有兴趣的同学可以了解下《MySQL如何使用MVCC解决脏读、不可重复读和幻读问题

一致性

一个事务就是一系列在逻辑上相关的操作的指令的集合,用于完成一项任务,其本质是将数据库中的数据从一种一致性状态转换到另一种一致性状态,以体现现实世界中的状态变化。

一致性是事务追求的最终目标。

比如商场中一共有100件商品,总售价5000元,这就是一个一致性状态,在售卖或者用户退款退货的过程中,无论处于什么状态,商场能卖出的商品总计一定是100件,不能多卖也不能少卖,能卖出的总售价是5000元,不能多也不能少。

总结

以上就是关于MySQL事务及ACID四大特性详解相关的全部内容,希望对你有帮助。欢迎持续关注潘子夜个人博客(www.panziye.com),学习愉快哦!


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/back/11680.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】