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. 模式配置

模式缓存操作适用场景
allINSERT、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;

结果

场景无Change Buffer有Change Buffer提升
批量插入(100万行)300秒180秒40%
随机更新(10万次)150秒90秒40%
批量删除(50万行)200秒120秒40%

监控Change Buffer

1. 查看状态

-- 查看Change Buffer状态
SHOW ENGINE INNODB STATUS\G

-- 关键指标
INSERT BUFFER AND ADAPTIVE HASH INDEX
Ibuf: size 1, free list len 0, seg size 2,
12345 merges
merged operations:
 insert 10000, delete mark 2000, delete 500
discarded operations:
 insert 100, delete mark 20, delete 10

指标说明

指标含义
sizeChange Buffer当前大小(页数)
merges已合并次数
merged operations已合并的操作数
insert缓存的插入操作数
delete mark缓存的删除标记数
delete缓存的物理删除数

2. 查看合并速度

-- 查看合并统计
SHOW GLOBAL STATUS LIKE 'Innodb_ibuf%';

-- 关键指标
Innodb_ibuf_size             : 1        -- 当前大小
Innodb_ibuf_free_list        : 0        -- 空闲列表长度
Innodb_ibuf_segment_size     : 2        -- 段大小
Innodb_ibuf_merges           : 12345    -- 合并次数
Innodb_ibuf_merged_inserts   : 10000    -- 已合并插入
Innodb_ibuf_merged_deletes   : 2000     -- 已合并删除
Innodb_ibuf_merged_delete_marks : 500   -- 已合并删除标记

实战建议

1. 适用场景

✅ 适合使用Change Buffer

-- 场景1:批量插入(日志表、监控数据)
INSERT INTO access_log (user_id, url, access_time)
VALUES (...);

-- 场景2:大量随机更新
UPDATE orders SET status = 'PAID' WHERE id = ?;

-- 场景3:非唯一索引多的表
CREATE TABLE products (
    id INT PRIMARY KEY,
    name VARCHAR(100),
    category VARCHAR(50),
    price DECIMAL(10,2),
    INDEX idx_category (category),  -- 非唯一索引
    INDEX idx_price (price)         -- 非唯一索引
);

❌ 不适合Change Buffer

-- 场景1:唯一索引多的表
CREATE TABLE users (
    id INT PRIMARY KEY,
    email VARCHAR(100) UNIQUE,    -- 唯一索引,无法使用Change Buffer
    phone VARCHAR(20) UNIQUE       -- 唯一索引
);

-- 场景2:读多写少的表
-- Change Buffer对读性能无帮助,反而增加合并开销

2. 配置建议

# 写多读少的场景(日志、监控)
innodb_change_buffering = all
innodb_change_buffer_max_size = 50  -- 增大到50%

# 读多写少的场景(用户表、配置表)
innodb_change_buffering = none      -- 禁用
innodb_change_buffer_max_size = 25

3. 监控建议

# Prometheus监控
innodb_ibuf_merges_total
innodb_ibuf_merged_inserts_total

# 告警规则(合并速度过慢)
rate(innodb_ibuf_merges_total[5m]) < 10

Change Buffer vs Buffer Pool

特性Change BufferBuffer Pool
作用缓存索引修改操作缓存数据页和索引页
适用对象非唯一二级索引所有数据页
触发条件索引页不在Buffer Pool时所有读写操作
性能提升减少随机IO(写优化)减少磁盘IO(读写优化)
大小Buffer Pool的25%(默认)128MB-数百GB

常见面试题

Q1: Change Buffer的作用是什么?

  • 缓存非唯一二级索引的修改操作,减少随机IO

Q2: 为什么唯一索引不能使用Change Buffer?

  • 唯一索引需要立即检查唯一性,必须读取索引页,无法延迟合并

Q3: Change Buffer何时合并?

  • 查询需要访问索引页时
  • 后台线程定期合并
  • Change Buffer空间不足
  • MySQL正常关闭

Q4: 如何判断是否应该使用Change Buffer?

  • 非唯一索引多、写多读少、批量插入场景适合
  • 唯一索引多、读多写少场景不适合

小结

Change Buffer:缓存非唯一二级索引修改,减少随机IO ✅ 适用场景:批量插入、随机更新、非唯一索引多 ✅ 不适用场景:唯一索引、读多写少 ✅ 配置建议:写多读少增大到50%,读多写少禁用

Change Buffer是InnoDB写优化的重要机制。


📚 相关阅读

  • 下一篇:《Adaptive Hash Index:自适应哈希索引》
  • 推荐:《Buffer Pool:MySQL的内存管理》