innodb 删除隐藏列_MySQL进阶之InnoDB事务原子性实现原理

概述

事务(Transaction)是访问和更新数据库的程序执行单元;事务中可能包含一个或多个语句,这些语句要么都执行,要么都不执行,事务是保证数据一致性的重要手段。事务具有4个属性,就是原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),简称ACID属性。MySQL的事务是由存储引擎实现的,支持事务的数据库引擎包括InnoDB、NDB Cluster等,其中InnoDB的使用最为广泛。下面以InnoDB为基础进行描述,剖析InnoDB实现事务特性的原理。

事务提交与回滚

首先,回顾以下在MySQL中显式使用事务的方式

-- 开启事务start transaction;...-- 一条或多条DML或DDL语句select | insert | update | delete...-- 提交或回滚commit | rollback;

MySQL的InnoDB存储引擎默认是自动提交事务,可以通过下面的设置和查看

-- 关闭自动提交set autocommit = 0;-- 开启自动提交set autocommit = 1;-- 查看自动提交变量状态show variables like 'autocommit';

在自动提交模式下,如果没有start transaction显式地开始一个事务,那么每个sql语句都会被当做一个事务执行提交操作。

在MySQL中,存在一些特殊的命令,如果在事务中执行了这些命令,会马上强制执行commit提交事务务,比如如DDL语句create table、drop table、alter table、lock tables语句等。对于常用的select、insert、update和delete命令,则不会强制提交事务。

原子性实现原理

原子性是指一个事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做;如果事务中一个语句执行失败,则已执行的语句也必须回滚,数据库退回到事务前的状态。InnoDB主要基于回滚日志实现事务的原子性。

  • 隐藏字段

在InnoDB中,每一行数据除了包括我们设计的字段之外,还包含了一些内部字段,也叫隐藏字段。比如在InnoDB的聚簇索引记录中会包含3个隐藏列row_id、trx_id、roll_pointer等

row_id

数据行id,用于标识一行数据。row_id并不是必要的,如果创建的表中有主键或者非NULL唯一键时都不会包含row_id列

trx_id

事务id,每次对某数据记录进行修改时,都会把对应的事务id赋值给trx_id列。每次事务操作都会分配一个事务id,它是一个自增id。

roll_pointer

当前数据记录的上一个版本的指针。每次对某条数据记录进行改动时,都会把旧版本数据记录按照一定格式写入到回滚日志(undo log) 中,而roll_pointer列则保存了该旧版本数据记录在回滚日志中的位置,相当于一个指针。

比如一个user表,目前只有一个id和name字段,id为主键。则InnoDB中表示一行数据的结构如下

30acf6836b68ab8e6f8de00a6d8310bd.png

表示一行数据的数据结构

回滚日志(undo log)与版本链

MySQL架构设计当中,其有一个完整的日志体系,包括二进制日志、通用查询日志、慢查询日志等,为排查问题、性能优化、实现事务控制等提供了强大的支撑。在这些日志当中,和事务直接相关的日志有两个,一个是重做日志(redo log),一个是回滚日志(undo log)。其中的回滚日志,是事务原子性和隔离性实现的基础。

回滚日志分为两类,一类是插入回滚日志(insert undo log),一类是更新回滚日志(update undo log)。插入回滚日志由insert操作产生的,在事务提交后可以被立即丢弃;更新回滚日志则是由update和delete操作产生的,更新回滚日志不仅在事务回滚时使用,在InnoDB实现事务隔离性时也需要用到,所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除。

比如以下三条语句

insert into user (id,name) values (1,'蕃豆咖啡');start transaction;update user set name = 'coffee' where id = 1;update user set name = '最新的名字' where id = 1;commit;

其产生的回滚日志内容如下图所示

c0a26b23b37bf5ddca281c577e8211d3.png

回滚日志和版本链

每次对数据记录进行修改时(事务操作),都会产生一条回滚日志 ,每条回滚日志也都有一个roll_pointer属性(insert操作对应的回滚日志没有该属性,因为该记录并没有更早的版本),随着更新次数的增多,所有的版本都会被roll_pointer属性连接成一个链表,这个链表称之为版本链。版本链的头节点就是当前记录最新的值。注意,每个版本中还包含生成该版本时对应的事务id,这个信息很重要,是InnoDB实现事务隔离性的关键部分。

  • 实现原理

实现原子性的关键,是当事务回滚时能够撤销所有已经成功执行的sql语句。当开启一个事务对数据进行增删改时,InnoDB会生成一条对应的回滚日志(修改前的数据信息)到回滚日志缓冲中。如果事务执行失败或调用了rollback命令,导致事务需要回滚时,InnoDB就会根据回滚日志中的信息,执行与当前事务相反的工作,将数据回滚到增删改之前的状态。

InnoDB的回滚实现原理,简单理解,就是在需要回滚事务时,InnoDB根据回滚日志的信息,对于每个insert语句,执行一条对应的delete语句;对于每个update语句,会执行一条更新为原值的update语句;对于每条delete语句,会执行一条对应的insert语句。

这样,通过回滚日志,InnoDB可以在任何需要回滚的地方将相关数据回滚到事务前的状态。

  • 回滚日志示例

比如创建一条新的用户记录

insert into user (id,name) values (1,'蕃豆咖啡');

当需要回滚时,InnoDB将根据回滚日志会生成一条删除语句并执行

delete from user where id = 1;

再比如更新该用户名字

update user set name = 'coffee' where id = 1;

当需要回滚时,InnoDB将根据回滚日志会生成一条更新语句并执行

-- InnoDB根据update语句所更新的主键id、更新列name、列更新前内容(蕃豆咖啡)-- 生成对应的回滚日志update user set name = '蕃豆咖啡' where id = 1;

如要删除该用户记录时

delete from user where id = 1;

当需要回滚时,InnoDB将根据回滚日志会生成一条插入语句并执行

insert into user (id,name) values (1,'蕃豆咖啡');

总结

InnoDB实现回滚的关键是回滚日志。回滚的过程比上面描述的会复杂一些,比如在并发情况下,多个事务之间,回滚时需要区分当前事务可见的回滚日志,然后再根据回滚日志进行回滚,这实际上已经属于事务隔离性的范畴了,这里暂不深入探讨。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值