1、 UPDATE操作对应的undo日志
- 在执行update语句时,InnoDB对更新主键和不更新主键这两种情况有截然不同的处理方式。
2、不更新主键的情况
(1)、就地更新
- 在更新记录时,对于被更新的每个列来说,如果更新后的列与更新前的列占用的存储空间一样大,那么可以进行就地更新,也就是直接在原记录的基础上修改对应列的值。
(2)、先删除旧记录,再插入新记录
-
如果有任何一个被更新的列在更新前后占用的存储空间大小不一致,那么就需要先把这条旧记录从聚簇索引页面中删除,然后在根据更新后列的值创建一条新的记录并插入到页面中。 -
这里的删除,是真正的删除,也就是把这条记录从正常记录链表中移除并加入到垃圾链表中。 -
是由用户线程同步执行真正的删除操作,而不是DELETE语句中进行purge操作时使用的专门线程。 -
如果新创建的记录占用的存储空间不超过旧记录占用的空间,那么可以直接重用加入到垃圾链表中的旧记录所占用的存储空间,否则,需要在页面中新申请一块空间供新记录使用。 -
如果本页面已经没有可用的空间,就需要进行页面分裂操作,然后再插入新的记录。 -
更新操作对应TRX_UNDO_UPD_EXIST_REC类型的undo日志结构,如下图所示: -
注:上图解释如下 -
info bits:记录头信息的前4个比特的值。 -
trx_id:旧记录的trx_id值。 -
roll_pointer:旧记录的roll_pointer值。 -
len of index_col_info:也就是下边的【索引列各列信息】部分和本部分占用的存储空间总和。 -
索引列各列信息 <pos, len, value>列表:凡是被索引的列的各列信息。 -
n_updated:表示本条UPDATE语句执行后将有几个列被更新 -
被更新的列更新前信息 <pos, old_len, old_value>列表:被更新列在记录中的位置、更新前该列占用的存储空间大小、更新前该列的真实值。
(3)、更新操作生成undo日志的示例
# 显示开启一个事务,假设该事务的事务id为100
BEGIN;
# 插入两条记录
INSERT INTO sys_user(id, name, city) VALUES(1, 'xz','北京市'), (2, 'tom','天津市');
# 删除一条记录
DELETE FROM sys_user WHERE id = 1;
# 更新一条记录
UPDATE sys_user SET name='jack', city='上海市' where id = 2;
- 上图解释说明
- 这个UPDATE语句更新的列的大小都没有改动,所以可以采用就地更新的方式来执行。
- 在真正改动页面记录前,会先记录一条类型为TRX_UNDO_UPD_EXIST_REC的undo日志。
- 其中,<0, 4, 2><3, 3, ‘tom’>占用空间等于:(1+1+4)+(1+1+3) =11,最后,再加上len of index_col_info属性本身占2个字节,所以总共13字节。即:len of index_col_info=13。
3、更新主键的情况
- 步骤一:将旧记录进行delete mark操作
(1)、此时仅执行delete mark操作。而在事务提交后,才由专门的线程执行purge操作,从而把它加入到垃圾链表中。 (2)、之所以只对旧记录执行delete mark操作,是因为别的事务也可能同时访问这条记录,如果把它真正删除并加入到垃圾链表后,别的事务就访问不到了。这个功能就是MVCC。 - 步骤二:根据更新后各列的值创建一条新记录,并将其插入到聚簇索引中
(1)、针对UPDATE语句更新记录主键值的这种情况,在对该记录进行delete mark操作时,会记录一条类型为TRX_UNDO_DEL_MARK_REC的undo日志;之后插入新记录时,会记录一条类型为TRX_UNDO_INSERT_REC的undo日志。也就是说,每对一条记录的主键值进行改动,都会记录2条undo日志。
|