1.MySQL 核心模块揭秘 | 12 期 | 创建 savepoint
2.生产问题(三)Mysql for update 导致大量行锁
3.Mysql InnoDBåMyISAMçåºå«
4.慢查询日志中的源码 Lock_time 从哪里来?
5.MySQL全文索引源码剖析之Insert语句执行过程
6.MySQL 核心模块揭秘 | 13 期 | 回滚到 savepoint
MySQL 核心模块揭秘 | 12 期 | 创建 savepoint
回滚操作,除了回滚整个事务,解析还可以部分回滚。源码部分回滚,解析需要保存点(savepoint)的源码协助。本文我们先看看保存点里面都有什么。解析还原exe文件源码
作者:操盛春,源码爱可生技术专家,解析公众号『一树一溪』作者,源码专注于研究 MySQL 和 OceanBase 源码。解析 爱可生开源社区出品,源码原创内容未经授权不得随意使用,解析转载请联系小编并注明来源
本文基于 MySQL 8.0. 源码,源码存储引擎为 InnoDB。解析
InnoDB 的源码事务对象有一个名为undo_no 的属性。事务每次改变(插入、更新、删除)某个表的一条记录,都会产生一条 undo 日志。这条 undo 日志中会存储它自己的序号。这个序号就来源于事务对象的 undo_no 属性。
也就是说,事务对象的 undo_no 属性中保存着事务改变(插入、更新、删除)某个表中下一条记录产生的 undo 日志的序号。
每个事务都维护着各自独立的 undo 日志序号,和其它事务无关。
每个事务的 undo 日志序号都从 0 开始。事务产生的第 1 条 undo 日志的序号为 0,第 2 条 undo 日志的序号为 1,依此类推。
InnoDB 的 savepoint 结构中会保存创建 savepoint 时事务对象的 undo_no 属性值。
我们通过 SQL 语句创建一个 savepoint 时,server 层、binlog、InnoDB 会各自创建用于保存 savepoint 信息的结构。
server 层的 savepoint 结构是一个SAVEPOINT 类型的对象,主要属性如下:
binlog 的南瓜车app 源码 savepoint 结构很简单,是一个 8 字节的整数。这个整数的值,是创建 savepoint 时事务已经产生的 binlog 日志的字节数,也是接下来新产生的 binlog 日志写入 trx_cache 的 offset。
为了方便介绍,我们把这个整数值称为binlog offset。
InnoDB 的 savepoint 结构是一个trx_named_savept_t 类型的对象,主要属性如下:
创建 savepoint 时,server 层会分配一块 字节的内存,除了存放它自己的 SAVEPOINT 对象,还会存放 binlog offset 和 InnoDB 的 trx_named_savept_t 对象。
server 层的 SAVEPOINT 对象占用这块内存的前 字节,InnoDB 的 trx_named_savept_t 对象占用中间的 字节,binlog offset 占用最后的 8 字节。
客户端连接到 MySQL 之后,MySQL 会分配一个专门用于该连接的用户线程。
用户线程中有一个m_savepoints 链表,用户创建的多个 savepoint 通过 prev 属性形成链表,m_savepoints 就指向最新创建的 savepoint。
server 层创建 savepoint 之前,会按照创建时间从新到老,逐个查看链表中是否存在和本次创建的 savepoint 同名的 savepoint。
如果在用户线程的 m_savepoints 链表中找到了和本次创建的 savepoint 同名的 savepoint,需要先删除 m_savepoints 链表中的同名 savepoint。
找到的同名 savepoint,是 server 层的SAVEPOINT 对象,它后面的内存区域分别保存着 InnoDB 的 trx_named_savept_t 对象、binlog offset。
binlog 是个老实孩子,乖乖的把 binlog offset 写入了 server 层为它分配的内存里。删除同名 savepoint 时,不需要单独处理 binlog offset。
InnoDB 就不老实了,虽然 server 层也为 InnoDB 的 trx_named_savept_t 对象分配了内存,但是 InnoDB 并没有往里面写入内容。
事务执行过程中,用户每次创建一个 savepoint,千锋课程源码InnoDB 都会创建一个对应的 trx_named_savept_t 对象,并加入 InnoDB 事务对象的 trx_savepoints 链表的末尾。
因为 InnoDB 自己维护了一个存放 savepoint 结构的链表,server 层删除同名 savepoint 时,InnoDB 需要找到这个链表中对应的 savepoint 结构并删除,流程如下:
InnoDB 从事务对象的 trx_savepoints 链表中删除 trx_named_savept_t 对象之后,server 层接着从用户线程的 m_savepoints 链表中删除 server 层的SAVEPOINT 对象,也就连带着清理了 binlog offset。
处理完查找、删除同名 savepoint 之后,server 层就正式开始创建 savepoint 了,这个过程分为 3 步。
第 1 步,binlog 会生成一个 Query_log_event。
以创建名为test_savept 的 savepoint 为例,这个 event 的内容如下:
binlog event 写入 trx_cache 之后,binlog offset 会写入 server 层为它分配的 8 字节的内存中。
第 2 步,InnoDB 创建 trx_named_savept_t 对象,并放入事务对象的 trx_savepoints 链表的末尾。
trx_named_savept_t 对象的 name 属性值是 InnoDB 的 savepoint 名字。这个名字是根据 server 层为 InnoDB 的 trx_named_savept_t 对象分配的内存的地址计算得到的。
trx_named_savept_t 对象的savept 属性,是一个 trx_savept_t 类型的对象。这个对象里保存着创建 savepoint 时,事务对象中 undo_no 属性的值,也就是下一条 undo 日志的序号。
第 3 步,把 server 层的 SAVEPOINT 对象加入用户线程的 m_savepoints 链表的尾部。
server 层会创建一个SAVEPOINT 对象,用于存放 savepoint 信息。
binlog 会把binlog offset 写入 server 层为它分配的一块 8 字节的内存里。
InnoDB 会维护自己的 savepoint 链表,里面保存着trx_named_savept_t 对象。
如果 m_savepoints 链表中存在和本次创建的 savepoint 同名的 savepoint, 创建新的 savepoint 之前,server 层会从链表中删除这个同名的源码交付后说明 savepoint。
server 层创建的 SAVEPOINT 对象会放入m_savepoints 链表的末尾。
InnoDB 创建的 trx_named_savept_t 对象会放入事务对象的trx_savepoints 链表的末尾。
生产问题(三)Mysql for update 导致大量行锁
在一次复盘会上,我的同事分享了一次因误用了 Mysql 的 for update 命令,导致大量行锁产生的问题。这一情况引起了我的深思,因为在网上广泛流传的观点认为 InnoDB 存在锁升级,表锁的产生与数据量、锁的类型有关。然而,这一观点基于对《高性能 Mysql》和《Innodb 存储引擎》的深入理解,实则存在误解。在环境设定为 Mysql5.7,隔离级别为 RC 的情况下,我发现 for update 一个不存在的 where 条件时,InnoDB 加的是 Record 级别的锁。这一点通过执行查询信息得到验证,信息显示两个事务加的都是行级别锁。
对于锁住的行数量与数据的准确性,我注意到在相关书籍中提到的锁数据统计方式可能不够精确。而当 for update 命令无法根据索引加锁时,InnoDB 实际上会在行数据中进行搜索,并对主键进行加锁,而不是整个表。这种设计的初衷可能是为了提升效率,但具体设计考虑则无从得知。
在 InnoDB 加锁机制中,锁的获取遵循从上至下的逻辑,即自动加锁只包括表级别的意向锁和行级锁。表级别的意向锁仅阻塞全表扫描,不会影响其他操作。在 RR 隔离级别下,InnoDB 会采取 GapLock 和 Next-keyLock 算法,以防止插入数据导致的幻读问题,但这一操作并非绝对,对于唯一索引而言,InnoDB 可能会降低级别使用行级锁,gif转webp源码避免锁住范围。
总结来看,基于对实际操作的验证和对权威书籍的理解,可以得出以下
在 RC 级别下,InnoDB 的加锁机制主要为行级锁,表级的意向锁仅阻塞全表扫描。InnoDB 并不存在锁升级的现象。当 for update 命令未能通过索引加锁时,InnoDB 会在行数据中对主键进行加锁。通过这一分析,我与 DBA 进行了深入讨论,对这些观点进行了确认。对于这一问题,我鼓励大家在实际操作、阅读权威书籍和源码时,保持批判性思考,避免盲从网上信息。书籍与源码的阅读应结合实际经验,因为个人理解能力的差异可能导致理解方向的偏差。
原创声明:
本文章由我独立创作,内容均为原创。
如有需要转载或使用本文章内容,请注明出处。
Mysql InnoDBåMyISAMçåºå«
ããInnoDBåMyISAMæ¯å¨ä½¿ç¨MySQLæ常ç¨ç两个表类åï¼åæä¼ç¼ºç¹ï¼è§å ·ä½åºç¨èå®ãåºæ¬çå·®å«ä¸ºï¼MyISAMç±»åä¸æ¯æäºå¡å¤ççé«çº§å¤çï¼èInnoDBç±»åæ¯æãMyISAMç±»åç表强è°çæ¯æ§è½ï¼å ¶æ§è¡æ°åº¦æ¯InnoDBç±»åæ´å¿«ï¼ä½æ¯ä¸æä¾äºå¡æ¯æï¼èInnoDBæä¾äºå¡æ¯æå·²ç»å¤é¨é®çé«çº§æ°æ®åºåè½ã
ããMyIASMæ¯IASM表çæ°çæ¬ï¼æå¦ä¸æ©å±ï¼
ããäºè¿å¶å±æ¬¡çå¯ç§»æ¤æ§ã
ããNULLåç´¢å¼ã
ãã对åé¿è¡æ¯ISAM表ææ´å°çç¢çã
ããæ¯æ大æ件ã
ããæ´å¥½çç´¢å¼å缩ã
ããæ´å¥½çé®åç»è®¡åå¸ã
ããæ´å¥½åæ´å¿«çauto_incrementå¤çã
ãã以ä¸æ¯ä¸äºç»èåå ·ä½å®ç°çå·®å«ï¼
ãã1.InnoDBä¸æ¯æFULLTEXTç±»åçç´¢å¼ã
ãã2.InnoDBä¸ä¸ä¿å表çå ·ä½è¡æ°ï¼ä¹å°±æ¯è¯´ï¼æ§è¡select count(*) from tableæ¶ï¼InnoDBè¦æ«æä¸éæ´ä¸ªè¡¨æ¥è®¡ç®æå¤å°è¡ï¼ä½æ¯MyISAMåªè¦ç®åç读åºä¿å好çè¡æ°å³å¯ã注æçæ¯ï¼å½count(*)è¯å¥å å«whereæ¡ä»¶æ¶ï¼ä¸¤ç§è¡¨çæä½æ¯ä¸æ ·çã
ãã3.对äºAUTO_INCREMENTç±»åçå段ï¼InnoDBä¸å¿ é¡»å å«åªæ该å段çç´¢å¼ï¼ä½æ¯å¨MyISAM表ä¸ï¼å¯ä»¥åå ¶ä»å段ä¸èµ·å»ºç«èåç´¢å¼ã
ãã4.DELETE FROM tableæ¶ï¼InnoDBä¸ä¼éæ°å»ºç«è¡¨ï¼èæ¯ä¸è¡ä¸è¡çå é¤ã
ãã5.LOAD TABLE FROM MASTERæä½å¯¹InnoDBæ¯ä¸èµ·ä½ç¨çï¼è§£å³æ¹æ³æ¯é¦å æInnoDB表æ¹æMyISAM表ï¼å¯¼å ¥æ°æ®ååæ¹æInnoDB表ï¼ä½æ¯å¯¹äºä½¿ç¨çé¢å¤çInnoDBç¹æ§ï¼ä¾å¦å¤é®ï¼ç表ä¸éç¨ã
ããå¦å¤ï¼InnoDB表çè¡éä¹ä¸æ¯ç»å¯¹çï¼å¦æå¨æ§è¡ä¸ä¸ªSQLè¯å¥æ¶MySQLä¸è½ç¡®å®è¦æ«æçèå´ï¼InnoDB表åæ ·ä¼éå ¨è¡¨ï¼ä¾å¦update table set num=1 where name like â%aaa%â
ããä»»ä½ä¸ç§è¡¨é½ä¸æ¯ä¸è½çï¼åªç¨æ°å½çé对ä¸å¡ç±»åæ¥éæ©åéç表类åï¼æè½æ大çåæ¥MySQLçæ§è½ä¼å¿.
ãã
ããMySQLä¸MyISAMå¼æä¸InnoDBå¼ææ§è½ç®åæµè¯
ãã[硬件é ç½®]
ããCPU : AMD+ (1.8G)
ããå å: 1G/ç°ä»£
ãã硬ç: G/IDE
ãã[软件é ç½®]
ããOS : Windows XP SP2
ããSE : PHP5.2.1
ããDB : MySQL5.0.
ããWeb: IIS6
ãã[MySQL表ç»æ]
ããCREATE TABLE `myisam` (
ãã`id` int() NOT NULL auto_increment,
ãã`name` varchar() default NULL,
ãã`content` text,
ããPRIMARY KEY (`id`)
ãã) ENGINE=MyISAM DEFAULT CHARSET=gbk;
ããCREATE TABLE `innodb` (
ãã`id` int() NOT NULL auto_increment,
ãã`name` varchar() default NULL,
ãã`content` text,
ããPRIMARY KEY (`id`)
ãã) ENGINE=InnoDB DEFAULT CHARSET=gbk;
ãã[æ°æ®å 容]
ãã$name = "heiyeluren";
ãã$content = "MySQLæ¯ææ°ä¸ªåå¨å¼æä½ä¸ºå¯¹ä¸å表çç±»åçå¤çå¨ãMySQLåå¨å¼æå æ¬å¤çäºå¡å®å ¨è¡¨çå¼æåå¤çéäºå¡å®å ¨è¡¨çå¼æï¼Â· MyISAM管çéäºå¡è¡¨ãå®æä¾é«éåå¨åæ£ç´¢ï¼ä»¥åå ¨ææç´¢è½åãMyISAMå¨ææMySQLé ç½®é被æ¯æï¼å®æ¯é»è®¤çåå¨å¼æï¼é¤éä½ é ç½®MySQLé»è®¤ä½¿ç¨å¦å¤ä¸ä¸ªå¼æã ·MEMORYåå¨å¼ææä¾âå åä¸â表ãMERGEåå¨å¼æå 许éåå°è¢«å¤çåæ ·çMyISAM表ä½ä¸ºä¸ä¸ªåç¬ç表ãå°±åMyISAMä¸æ ·ï¼MEMORYåMERGEåå¨å¼æå¤çéäºå¡è¡¨ï¼è¿ä¸¤ä¸ªå¼æä¹é½è¢«é»è®¤å å«å¨MySQLä¸ã éï¼MEMORYåå¨å¼ææ£å¼å°è¢«ç¡®å®ä¸ºHEAPå¼æã· InnoDBåBDBåå¨å¼ææä¾äºå¡å®å ¨è¡¨ãBDB被å å«å¨ä¸ºæ¯æå®çæä½ç³»ç»åå¸çMySQL-Maxäºè¿å¶ååçéãInnoDBä¹é»è®¤è¢«å æ¬å¨ææMySQL 5.1äºè¿å¶ååçéï¼ä½ å¯ä»¥æç §å好éè¿é ç½®MySQLæ¥å 许æç¦æ¢ä»»ä¸å¼æã·EXAMPLEåå¨å¼ææ¯ä¸ä¸ªâåæ ¹âå¼æï¼å®ä¸åä»ä¹ãä½ å¯ä»¥ç¨è¿ä¸ªå¼æå建表ï¼ä½æ²¡ææ°æ®è¢«åå¨äºå ¶ä¸æä»å ¶ä¸æ£ç´¢ãè¿ä¸ªå¼æçç®çæ¯æå¡ï¼å¨MySQLæºä»£ç ä¸çä¸ä¸ªä¾åï¼å®æ¼ç¤ºè¯´æå¦ä½å¼å§ç¼åæ°åå¨å¼æãåæ ·ï¼å®ç主è¦å ´è¶£æ¯å¯¹å¼åè ã";
ãã[æå ¥æ°æ®-1] (innodb_flush_log_at_trx_commit=1)
ããMyISAM 1Wï¼3/s
ããInnoDB 1Wï¼/s
ããMyISAM Wï¼/s
ããInnoDB Wï¼/s
ããMyISAM Wï¼/s
ããInnoDB Wï¼æ²¡æ¢æµè¯
ãã[æå ¥æ°æ®-2] (innodb_flush_log_at_trx_commit=0)
ããMyISAM 1Wï¼3/s
ããInnoDB 1Wï¼3/s
ããMyISAM Wï¼/s
ããInnoDB Wï¼/s
ããMyISAM Wï¼/s
ããInnoDB Wï¼/s
ãã[æå ¥æ°æ®3] (innodb_buffer_pool_size=M)
ããInnoDB 1Wï¼3/s
ããInnoDB Wï¼/s
ããInnoDB Wï¼/s
ãã[æå ¥æ°æ®4] (innodb_buffer_pool_size=M, innodb_flush_log_at_trx_commit=1, set autocommit=0)
ããInnoDB 1Wï¼3/s
ããInnoDB Wï¼/s
ããInnoDB Wï¼/s
ãã[MySQL é ç½®æ件] (缺çé ç½®)
ãã# MySQL Server Instance Configuration File
ãã[client]
ããport=
ãã[mysql]
ããdefault-character-set=gbk
ãã[mysqld]
ããport=
ããbasedir="C:/mysql/"
ããdatadir="C:/mysql/Data/"
ããdefault-character-set=gbk
ããdefault-storage-engine=INNODB
ããsql-mode="STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"
ããmax_connections=
ããquery_cache_size=0
ããtable_cache=
ããtmp_table_size=M
ããthread_cache_size=8
ããmyisam_max_sort_file_size=G
ããmyisam_max_extra_sort_file_size=G
ããmyisam_sort_buffer_size=M
ããkey_buffer_size=M
ããread_buffer_size=K
ããread_rnd_buffer_size=K
ããsort_buffer_size=K
ããinnodb_additional_mem_pool_size=4M
ããinnodb_flush_log_at_trx_commit=1
ããinnodb_log_buffer_size=2M
ããinnodb_buffer_pool_size=M
ããinnodb_log_file_size=M
ããinnodb_thread_concurrency=8
ãããæ»ç»ã
ããå¯ä»¥çåºå¨MySQL 5.0éé¢ï¼MyISAMåInnoDBåå¨å¼ææ§è½å·®å«å¹¶ä¸æ¯å¾å¤§ï¼é对InnoDBæ¥è¯´ï¼å½±åæ§è½ç主è¦æ¯ innodb_flush_log_at_trx_commit è¿ä¸ªé项ï¼å¦æ设置为1çè¯ï¼é£ä¹æ¯æ¬¡æå ¥æ°æ®çæ¶åé½ä¼èªå¨æ交ï¼å¯¼è´æ§è½æ¥å§ä¸éï¼åºè¯¥æ¯è·å·æ°æ¥å¿æå ³ç³»ï¼è®¾ç½®ä¸º0æçè½å¤çå°ææ¾æåï¼å½ç¶ï¼åæ ·ä½ å¯ä»¥SQLä¸æ交âSET AUTOCOMMIT = 0âæ¥è®¾ç½®è¾¾å°å¥½çæ§è½ãå¦å¤ï¼è¿å¬è¯´éè¿è®¾ç½®innodb_buffer_pool_sizeè½å¤æåInnoDBçæ§è½ï¼ä½æ¯ææµè¯åç°æ²¡æç¹å«ææ¾çæåã
ããåºæ¬ä¸æ们å¯ä»¥èè使ç¨InnoDBæ¥æ¿ä»£æ们çMyISAMå¼æäºï¼å 为InnoDBèªèº«å¾å¤è¯å¥½çç¹ç¹ï¼æ¯å¦äºå¡æ¯æãåå¨è¿ç¨ãè§å¾ãè¡çº§éå®ççï¼å¨å¹¶åå¾å¤çæ åµä¸ï¼ç¸ä¿¡InnoDBç表ç°è¯å®è¦æ¯MyISAM强å¾å¤ï¼å½ç¶ï¼ç¸åºçå¨my.cnfä¸çé ç½®ä¹æ¯æ¯è¾å ³é®çï¼è¯å¥½çé ç½®ï¼è½å¤ææçå éä½ çåºç¨ã
ããå¦æä¸æ¯å¾å¤æçWebåºç¨ï¼éå ³é®åºç¨ï¼è¿æ¯å¯ä»¥ç»§ç»èèMyISAMçï¼è¿ä¸ªå ·ä½æ åµå¯ä»¥èªå·±æé ã
慢查询日志中的 Lock_time 从哪里来?
在探讨慢查询日志中的 Lock_time 时,首先要了解的是,与这位老相识的关联并不仅限于它表示锁等待时间这一层面,而是深入理解 Lock_time 的构成以及它是如何被计算得出的。
深入研究 Lock_time 之前,我们仅知其代表锁等待时间,却未探入其具体组成与计算过程。在众多 SQL 执行时间较长的场景中,我们时常困惑于 Lock_time 的数值似乎与其等待时间并不成正比,如观察到 Lock_time 仅为 0. 秒。
本文旨在揭开 Lock_time 的面纱,探索它包含的锁等待时间的来源以及计算方式。基于 MySQL 8.0. 源码,我们将重点放在 InnoDB 存储引擎上,通过分析其实现逻辑,以直观呈现 Lock_time 的组成与计算过程。
首先,明确 Lock_time 的计算方法。它由表锁等待时间和行锁等待时间两部分相加形成。这启示我们,对 Lock_time 的理解不仅限于表锁等待时间的考量,还需关注行锁等待时间的计算与累加。
在表锁等待时间的实现中,源码展示了从 Sql_cmd_dml::execute() 函数开始,到 lock_external() 方法的执行,整个过程涉及多个函数和调用,包括 lock_tables()、mysql_lock_tables() 等。这一系列操作最终在 thd->inc_lock_usec() 函数中实现了表锁等待时间的累积。
值得注意的是,表锁等待时间并非单纯反映锁等待过程,它还包含了执行一些初始化操作所需的时间,如同步刷脏页操作。对于 FLUSH TABLES ... WITH READ LOCK 语句,表锁等待时间更包含了将涉及的表所属表空间的脏页同步到磁盘所需的时间。
行锁等待时间的计算则更为直接,主要关注的是在加锁过程中记录的等待时间。通过 sel_set_rec_lock() 函数进行加锁操作,当其他事务持有该记录的行锁时,等待时间即开始计算。通过代码逻辑,我们可直观理解这一过程,即在获取行锁或等待超时后,通过当前时间减去开始时间计算得到行锁等待时间。
综上所述,Lock_time 由表锁与行锁等待时间共同构成,两者在累加的过程中最终形成 Lock_time。表锁等待时间不仅包含锁等待时间本身,还可能涉及额外的初始化操作时间,而行锁等待时间则更为直接且精确。通过深入分析源码,我们对 Lock_time 的构成有了更全面的理解。
MySQL全文索引源码剖析之Insert语句执行过程
本文来源于华为云社区,作者为GaussDB数据库,探讨了MySQL全文索引源码中Insert语句的执行过程。
全文索引是一种常用于信息检索的技术,它通过倒排索引实现,即单词和文档的映射关系,如(单词,(文档,偏移))。以创建一个表并在opening_line列上建立全文索引为例,插入'Call me Ishmael.'时,文档会被分为'call', 'me', 'ishmael'等单词,并记录在全文索引中。
全文索引Cache的作用类似于Change Buffer,用于缓存分词结果,避免频繁刷盘。Innodb使用fts_cache_t结构来管理cache,每个全文索引的表都会在内存中创建一个fts_cache_t对象。
Insert语句的执行分为三个阶段:写入行记录阶段、事务提交阶段和刷脏阶段。写入行记录阶段生成doc_id并写入Innodb的行记录,并将doc_id缓存。事务提交阶段对文档进行分词,获取{ 单词,(文档,偏移)}关联对,并插入到cache。刷脏阶段后台线程将cache刷新到磁盘。
全文索引的并发插入可能导致OOM问题,可通过修复patch #解决。当MySQL进程崩溃时,fts_init_index函数会恢复crash前的cache数据。
MySQL 核心模块揭秘 | 期 | 回滚到 savepoint
深入理解 MySQL,了解如何实现部分回滚操作。本文由技术专家操盛春撰写,他在公众号『一树一溪』分享 MySQL 和 OceanBase 源码研究。本文基于 MySQL 8.0.,InnoDB 存储引擎,探讨核心模块的工作原理。
首先,我们创建测试表并插入数据。关键操作分为四个阶段,编号为 SQL 1 至 SQL 4,其中 SQL 4 是讨论焦点。SQL 2 和 SQL 3 分别产生 undo 日志 0 和 1。
当执行事务时,产生的 binlog 日志在 trx cache 中。回滚整个事务时,需要清除这些日志。然而,实际操作中,binlog 回滚步骤看似简单,却并未执行真正清除,只是为后续的 InnoDB 回滚做准备。
InnoDB 回滚是关键环节,它会根据 undo 日志执行反向操作,恢复事务影响的数据。以 SQL 为例,会从最新的 undo 日志开始回滚,逐条执行反向操作,包括记录的删除。
回滚后,事务的执行状态需要通过提交事务来更新。这不同于 commit 语句,因为回滚操作已经改变了数据,即使从逻辑上看恢复了原样,也需要将 InnoDB 中的修改正式提交。
trx cache 中的 binlog 日志会在 InnoDB 回滚完成后进行清除,这个过程涉及内存 buffer 和磁盘临时文件。binlog 回滚步骤延迟到这个阶段,是因为在事务提交前,binlog 日志并不需要写入持久化存储。
总结起来,MySQL 的部分回滚包括:无实际动作的 binlog 回滚,执行 InnoDB 回滚恢复数据,然后提交 InnoDB 事务,最后清理 trx cache 中的临时 binlog。如果你对文中内容有疑问,欢迎留言交流。
对于 SQL 质量管理,如需更多工具支持,可以了解 SQLE,一个覆盖开发到生产环境的 SQL 管理平台,提供流程自动化和数据质量管理功能。
MySQL:排序(filesort)详细解析(字长文)
MySQL排序详解:深入理解filesort过程(简化版)
MySQL中的排序(filesort)是DBA工作中常见的操作,本文主要针对Innodb引擎,使用5.7.源码版本,针对快速排序和归并排序进行详细解析。filesort在执行计划中表示排序操作,但执行计划本身并不揭示所有细节。
首先,我们从一个问题出发,介绍一个朋友遇到的案例,排序后临时文件意外达G。我们将通过实例逐步分析排序的流程。
1. 确认排序字段:从order by语句开始,如"a2,a3",并存储在Filesort的sortorder中,涉及原始和修改的filesort算法,但本文不涉及复杂算法分支。
2. 计算sort字段长度:通过sortlength函数,考虑每个字段的长度,如varchar(),长度计算为字符数量的两倍。超过max_sort_length设置的字段将导致排序精度下降。
3. 确定addon字段空间:根据max_length_for_sort_data,判断是否使用回表排序算法。如a1、a2、a3都是需要的字段,且总长度超过字节,会使用回表排序。
4. 计算每行数据长度:考虑sort字段和addon字段,包括可能的打包压缩。在内存排序阶段,将数据按照计算出的长度存储。
5. 分配内存:根据sort_buffer_size和表大小,计算实际需要的内存,并进行内存排序。
6. 内存排序与外部归并:如果数据量大,内存排序后会写入临时文件,进行外部归并排序。
7. 排序方式总结:文件sort函数会输出排序方式,如sort_key+packed_additional_fields(不回表排序,打包字段)或sort_key+additional_fields(固定长度字段)。
8. 最终排序:可能生成额外的临时文件,存储归并排序结果,文件数量根据排序量变化。
9. 问题:original filesort算法的回表和Rows_examined的计算。
. 使用OPTIMIZER_TRACE查看排序结果,理解排序过程和使用的内存。
案例中,通过group by操作的排序,如果sort字段过大,会使用回表排序,导致临时文件占用巨大。总结排序过程包括了组织排序数据的方式、排序方法的选择、内存分配策略以及临时文件的管理。
理解排序过程对优化查询性能和避免大文件临时文件至关重要。通过合理设计和使用索引,以及优化排序策略,可以有效控制临时文件的大小。