MySQL采用B+树作为核心索引结构,其平衡多路搜索树的特性保证了O(logN)的查询效率。B+树具有以下显著特征:
以InnoDB引擎为例,其默认页大小为16KB。假设索引键为INT类型(4字节),指针6字节,则单个节点可存储约16KB/(4+6)=1600个键值,3层B+树即可支撑千万级数据量。
聚簇索引的物理存储顺序与索引顺序完全一致,具有以下特点:
非聚簇索引(二级索引)的检索需要两次查找:
-- 查询流程示例
SELECT * FROM table WHERE secondary_index = 'value';
索引覆盖是指查询所需的列都包含在索引中,避免回表操作:
-- 创建覆盖索引
ALTER TABLE orders ADD INDEX idx_customer_date(customer_id, order_date);
-- 覆盖查询
SELECT customer_id, order_date FROM orders
WHERE customer_id = 1001;
回表查询则发生在需要获取非索引列时:
-- 需要回表
SELECT * FROM orders
WHERE customer_id = 1001;
优化器采用基于成本的评估模型,核心决策流程包括:
成本估算模型参数:
选择性计算公式:
selectivity = distinct_values / total_rows
高选择性的列更适合创建索引。
MySQL在解析WHERE条件时,会将OR条件转换为多个独立查询的并集:
-- 原始查询
SELECT * FROM users
WHERE age > 30 OR salary > 100000;
-- 等效处理
SELECT * FROM users WHERE age > 30
UNION
SELECT * FROM users WHERE salary > 100000;
这种转换导致优化器难以使用单一索引。
当OR条件涉及不同索引列时,优化器可能选择全表扫描而非索引合并,原因包括:
AND条件天然适合索引使用:
-- 使用组合索引
ALTER TABLE products ADD INDEX idx_cat_price(category, price);
-- 高效查询
SELECT * FROM products
WHERE category = 'Electronics' AND price > 500;
而OR条件会破坏索引的最左前缀原则。
考虑以下查询:
SELECT * FROM orders
WHERE status = 'SHIPPED' OR total_amount > 1000;
若存在(status)和(total_amount)两个单列索引,B+树的遍历方式导致:
假设表有100万行数据:
全表扫描成本计算:
IO成本 = 数据页数量 * 1.0 = 1000000/200 ≈ 5000
CPU成本 = 行数 * 0.1 = 1000000 * 0.1 = 100000
总成本 = 5000 + 100000 = 105000
索引合并成本:
status索引IO = 100000/200 * 1.0 = 500
total_amount索引IO = 50000/200 * 1.0 = 250
合并CPU成本 = (100000+50000)*0.1 = 15000
去重成本 = 150000 * 0.2 = 30000
总成本 = 500+250+15000+30000=45750
此时优化器应选择索引合并,但实际可能因统计信息不准导致误判。
原始查询:
SELECT * FROM logs
WHERE level = 'ERROR' OR level = 'CRITICAL';
优化方案:
SELECT * FROM logs WHERE level = 'ERROR'
UNION ALL
SELECT * FROM logs WHERE level = 'CRITICAL'
执行计划对比:
MySQL 8.0引入跳跃扫描优化:
ALTER TABLE sales ADD INDEX idx_region_date(region, sale_date);
-- 8.0前需要全表扫描
SELECT * FROM sales
WHERE region = 'North' OR sale_date > '2023-01-01';
-- 8.0可能使用跳跃扫描
5.7版本使用虚拟列实现函数索引:
ALTER TABLE products
ADD COLUMN name_upper VARCHAR(255) AS (UPPER(name)) VIRTUAL,
ADD INDEX idx_name_upper(name_upper);
SELECT * FROM products
WHERE UPPER(name) = 'LAPTOP' OR name_upper = 'DESKTOP';
EXPLAIN SELECT * FROM users
WHERE phone = '13800138000' OR email = '[email protected]';
输出解读:
id | select_type | table | type | possible_keys | key | rows | Extra
1 | SIMPLE | users | ALL | idx_phone,idx_email | NULL | 98304 | Using where
表明优化器选择全表扫描,OR条件导致索引失效。
使用sysbench进行压力测试:
sysbench --mysql-host=127.0.0.1 --mysql-user=root \
--mysql-password=123456 --mysql-db=test \
--test=oltp_read_only --tables=10 \
--table-size=1000000 --threads=32 --time=300 \
--report-interval=10 --rand-type=uniform run
临时禁用索引进行测试:
ALTER TABLE orders ALTER INDEX idx_total_amount INVISIBLE;
-- 测试OR查询性能
SELECT * FROM orders
WHERE status = 'SHIPPED' OR total_amount > 1000;
ALTER TABLE orders ALTER INDEX idx_total_amount VISIBLE;
8.0支持真正的降序索引:
CREATE INDEX idx_price_desc ON products(price DESC);
-- 同时支持ASC和DESC的混合查询
SELECT * FROM products
WHERE price > 100 OR price < 50;