大数跨境
0
0

MySQL优化特性 ICP/MRR/BNL/BKA

MySQL优化特性  ICP/MRR/BNL/BKA 外贸队长JOJO
2025-10-07
3
导读:MySQL ICP/MRR/BNL/BKA1. Index Condition Pushdown (ICP 索

MySQL ICP/MRR/BNL/BKA

1. Index Condition Pushdown (ICP 索引条件下推)

作用:ICP 允许 WHERE 条件中的部分过滤操作在存储引擎层进行。当查询使用索引时,MySQL 将 WHERE 子句中可以由索引覆盖的过滤条件下推到存储引擎,从而 减少存储引擎层和 Server 层之间的数据传输量,降低回表次数。

适用条件:

  • 只适用于二级索引(对于 InnoDB 表)
  • 查询类型为 range、ref、eq_ref 和 ref_or_null
  • 不适用于覆盖索引场景

配置与使用

  -- 查看ICP当前设置
SELECT @@optimizer_switch LIKE '%index_condition_pushdown%';
  -- 开启ICP(MySQL 8.0默认开启)
SET optimizer_switch = 'index_condition_pushdown=on';
  -- 关闭ICP(用于性能对比测试)
SET optimizer_switch = 'index_condition_pushdown=off';

实例:

  -- 创建测试表
CREATE TABLE `icp_test` (
  `id` int PRIMARY KEY AUTO_INCREMENT,
  `a` int NOT NULL,
  `b` varchar(20) NOT NULL,
  `c` varchar(20) NOT NULL
) ENGINE=InnoDB;


ALTER TABLE icp_test ADD INDEX idx_a_b (a, b);


INSERT INTO icp_test (a, b, c)
SELECT 
  FLOOR(RAND() * 10) + 1,
  CONCAT('b', FLOOR(RAND() * 100)),
  CONCAT('c', FLOOR(RAND() * 1000))
FROM 
  (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t1,
  (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t2,
  (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t3;


SELECT COUNT(*) FROM icp_test;

Multi-Range Read (MRR 多范围读取)

作用:MRR 通过 将随机 I/O 转换为顺序 I/O 来优化查询性能。它先将二级索引查找到的主键 ID 收集到缓冲区,排序后再按照主键顺序访问数据行,显著减少磁盘随机访问。

适用场景:

  • 索引范围扫描和等值连接操作
  • 需要回表的查询
  • 特别是机械硬盘环境

配置与使用

  -- 查看MRR设置
SELECT @@optimizer_switch LIKE '%mrr%';
  -- 开启MRR并关闭基于成本的评估
SET optimizer_switch = 'mrr=on,mrr_cost_based=off';
  -- 调整MRR缓冲区大小(根据需要)
SET read_rnd_buffer_size = 262144; -- 256KB
  -- 创建测试表
CREATE TABLE mrr_test (
  id INT PRIMARY KEY AUTO_INCREMENT,
  i0 INT NOT NULL,
  i1 INT NOT NULL,
  c0 VARCHAR(100) NOT NULL
) ENGINE=InnoDB;

-- 创建辅助索引
ALTER TABLE mrr_test ADD INDEX idx_i0 (i0);

-- 插入测试数据
INSERT INTO mrr_test (i0, i1, c0)
SELECT 
  FLOOR(RAND() * 100),
  FLOOR(RAND() * 1000),
  CONCAT('content_', FLOOR(RAND() * 10000))
FROM 
  (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t1,
  (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t2,
  (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t3,
  (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t4;

SELECT COUNT(*) FROM mrr_test;

Block Nested-Loop (BNL 块嵌套循环)

作用:BNL 针对 无索引连接 场景优化,通过将驱动表数据分块放入 join_buffer,减少内表的扫描次数。相比传统的嵌套循环连接(NLJ),BNL 大幅降低了内表的访问频率。

适用条件:

  • 内表的查询类型是 ALL、index 或 range 时
  • 被关联表没有可用索引时的最后手段

配置与使用

  -- 查看BNL设置
SELECT @@optimizer_switch LIKE '%block_nested_loop%';
  -- 开启BNL(MySQL 8.0默认开启)
SET optimizer_switch = 'block_nested_loop=on';
  -- 调整连接缓冲区大小
SET join_buffer_size = 262144; -- 256KB
  -- 创建测试表
CREATE TABLE bnl_driver (
  id INT PRIMARY KEY AUTO_INCREMENT,
  key_col INT NOT NULL,
  data VARCHAR(50)
) ENGINE=InnoDB;

CREATE TABLE bnl_driven (
  id INT PRIMARY KEY AUTO_INCREMENT, 
  key_col INT NOT NULL,
  data VARCHAR(50)
) ENGINE=InnoDB;

-- 插入测试数据
INSERT INTO bnl_driver (key_col, data)
SELECT 
  FLOOR(RAND() * 100),
  CONCAT('driver_', FLOOR(RAND() * 1000))
FROM 
  (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t1,
  (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t2,
  (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t3;

INSERT INTO bnl_driven (key_col, data)  
SELECT 
  FLOOR(RAND() * 100),
  CONCAT('driven_', FLOOR(RAND() * 1000))
FROM 
  (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t1,
  (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t2,
  (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t3;


SELECT COUNT(*) FROM bnl_driver;
  -- 观察BNL使用
EXPLAIN SELECT * FROM bnl_driver d 
LEFT JOIN bnl_driven n ON d.key_col = n.key_col;
-- Extra列显示:Using join buffer (Block Nested Loop)
  -- 添加索引后对比(BNL将不再使用)
ALTER TABLE bnl_driven ADD INDEX idx_key_col(key_col);
EXPLAIN SELECT * FROM bnl_driver d 
LEFT JOIN bnl_driven n ON d.key_col = n.key_col;
-- Extra列不再显示Block Nested Loop

Batched Key Access (BKA 批量键访问)

作用:BKA 结合了 BNL 和 MRR 的优点,对 有索引的连接 进行批量处理。它先将驱动表的连接键批量提交给 MRR 接口,排序后按顺序访问被驱动表,将随机 I/O 转换为顺序 I/O。

适用条件:

  • 被驱动表能够使用索引
  • 内表的查询类型是 ref 或 eq_ref
  • BKA 依赖于 MRR,需要同时开启 MRR

配置与使用

  -- 开启BKA(需要同时开启MRR)
SET optimizer_switch = 'mrr=on,mrr_cost_based=off,batched_key_access=on';

-- 查看BKA设置
SELECT @@optimizer_switch LIKE '%batched_key_access%';
  CREATE TABLE bka_driver (
  id INT PRIMARY KEY AUTO_INCREMENT,
  join_key INT NOT NULL,
  data VARCHAR(50)
) ENGINE=InnoDB;

-- 插入驱动表数据
INSERT INTO bka_driver (join_key, data)
SELECT 
  FLOOR(RAND() * 100),
  CONCAT('data_', FLOOR(RAND() * 1000))
FROM 
  (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t1,
  (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t2,
  (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5) t3;

-- 确保mrr_test表有idx_i0索引
DROP INDEX IF EXISTS idx_i0 ON mrr_test;
ALTER TABLE mrr_test ADD INDEX idx_i0(i0);
  -- 观察BKA使用
EXPLAIN SELECT * FROM bka_driver d 
JOIN mrr_test m ON d.join_key = m.i0 
WHERE m.i0 BETWEEN 10 AND 30;
-- Extra列显示:Using join buffer (Batched Key Access)

-- 对比关闭BKA的情况
SET optimizer_switch = 'batched_key_access=off';
EXPLAIN SELECT * FROM bka_driver d 
JOIN mrr_test m ON d.join_key = m.i0 
WHERE m.i0 BETWEEN 10 AND 30;
-- Extra列不再显示Batched Key Access

【声明】内容源于网络
0
0
外贸队长JOJO
跨境分享地 | 每日分享实用知识
内容 45795
粉丝 2
外贸队长JOJO 跨境分享地 | 每日分享实用知识
总阅读279.1k
粉丝2
内容45.8k