Change Buffer概述 Change Buffer 是InnoDB的写优化机制,用于缓存非唯一二级索引的修改操作。
传统方式(每次修改立即更新索引): INSERT → 更新数据页 → 更新索引页(磁盘IO) Change Buffer方式(延迟更新索引): INSERT → 更新数据页 → 缓存到Change Buffer → 后台合并(merge) 为什么需要Change Buffer? 问题:二级索引随机IO CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(50), email VARCHAR(100), INDEX idx_email (email) -- 非唯一二级索引 ); -- 插入数据 INSERT INTO users VALUES (1, 'Alice', 'alice@a.com'); INSERT INTO users VALUES (100, 'Bob', 'bob@b.com'); INSERT INTO users VALUES (50, 'Charlie', 'charlie@c.com'); 问题:
主键索引:顺序插入(1 → 100 → 50,但按主键排序) email索引:随机插入(alice → bob → charlie,按email排序) 随机IO:email索引页分散在磁盘各处,需要多次磁盘寻址 Change Buffer解决方案:
缓存email索引的修改到内存 批量合并(merge),减少随机IO Change Buffer的工作原理 1. 插入流程 INSERT INTO users VALUES (101, 'David', 'david@d.com'); -- 流程 1. 更新主键索引(聚簇索引) - 直接写入Buffer Pool(缓存命中)或磁盘 2. 更新email索引(二级索引) - 检查索引页是否在Buffer Pool - ✅ 在:直接更新 - ❌ 不在:缓存到Change Buffer(不读磁盘) 3. 后台合并(merge) - 当索引页加载到Buffer Pool时 - 或系统空闲时 - 或Change Buffer满时 2. 合并(Merge)流程 触发条件: 1. 查询需要访问该索引页 2. 后台线程定期合并 3. Change Buffer空间不足 4. MySQL正常关闭 合并流程: 1. 加载索引页到Buffer Pool 2. 应用Change Buffer中的修改 3. 写回磁盘(或标记为脏页) 4. 清空Change Buffer对应条目 Change Buffer的限制 只适用于非唯一二级索引 -- ✅ 适用:非唯一索引 CREATE INDEX idx_name ON users(name); -- 可以使用Change Buffer -- ❌ 不适用:唯一索引 CREATE UNIQUE INDEX uk_email ON users(email); -- 无法使用Change Buffer -- 原因:需要读取索引页检查唯一性,无法延迟合并 -- ❌ 不适用:主键索引 -- 原因:主键是聚簇索引,数据直接存储在索引中 为什么唯一索引不能用? -- 插入数据 INSERT INTO users VALUES (102, 'Eve', 'eve@e.com'); -- 唯一索引检查流程 1. 检查email='eve@e.com'是否已存在 2. 必须读取email索引页(磁盘IO) 3. 如果不存在,才能插入 -- 无法使用Change Buffer 因为必须立即读取索引页,无法延迟合并 Change Buffer配置 1. 查看配置 -- 查看Change Buffer大小 SHOW VARIABLES LIKE 'innodb_change_buffer_max_size'; -- 25(默认,占Buffer Pool的25%) -- 查看Change Buffer模式 SHOW VARIABLES LIKE 'innodb_change_buffering'; -- all(默认) 2. 模式配置 模式 缓存操作 适用场景 all INSERT、DELETE、UPDATE(默认) 通用 none 禁用 唯一索引多的表 inserts 只缓存INSERT 只有插入场景 deletes 只缓存DELETE 批量删除场景 changes 缓存INSERT、UPDATE 插入更新场景 purges 缓存DELETE、PURGE 清理场景 -- 配置示例 # my.cnf [mysqld] innodb_change_buffering = all innodb_change_buffer_max_size = 25 -- 25% 性能对比 测试场景 -- 测试表(500万行,非唯一索引) CREATE TABLE test_change_buffer ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50), email VARCHAR(100), phone VARCHAR(20), INDEX idx_email (email), INDEX idx_phone (phone) ); -- 批量插入100万行 INSERT INTO test_change_buffer (name, email, phone) SELECT CONCAT('User', seq), CONCAT('user', seq, '@example.com'), CONCAT('1380000', seq) FROM seq_1_to_1000000; 结果:
...