面试题答案
一键面试优化查询以降低回表代价的方法
- 覆盖索引:由于需要查询非索引字段,而当前索引
idx_order_user_id_status (user_id, order_status)
不能直接覆盖所有需要查询的字段。可以考虑创建一个覆盖索引,例如idx_user_id_status_time (user_id, order_status, order_time)
,假设order_time
字段用于确定最近的订单。这样在查询时,MySQL可以直接从索引中获取所需的user_id
、order_status
和order_time
信息,减少回表操作。 - 子查询优化:先通过子查询利用索引筛选出每个用户最近的10个已完成订单的主键(假设主键为
order_id
),然后再根据这些主键查询完整的订单详细信息。示例SQL如下:
SELECT *
FROM order
WHERE order_id IN (
SELECT order_id
FROM (
SELECT order_id, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY order_time DESC) AS rn
FROM order
WHERE order_status = 'completed'
) AS sub
WHERE rn <= 10
);
在这个子查询中,PARTITION BY user_id ORDER BY order_time DESC
确保对每个用户按订单时间倒序排列,ROW_NUMBER()
生成行号,然后外层查询根据行号筛选出每个用户最近的10个订单。
MySQL底层执行原理分析
索引结构
MySQL常用的索引结构是B+树。B+树索引的叶子节点包含了索引键值以及指向数据行的指针(在聚簇索引中,叶子节点直接包含数据行)。对于非聚簇索引(如我们创建的idx_order_user_id_status
和idx_user_id_status_time
),叶子节点存储索引列的值和对应的主键值(用于回表操作)。B+树的结构特点使得范围查询和排序操作相对高效,因为数据在叶子节点是按顺序存储的。
数据读取方式
- 索引扫描:当使用索引进行查询时,MySQL首先在B+树索引中定位到符合条件的索引项。例如,在
idx_order_user_id_status
索引中查找order_status = 'completed'
的记录时,会从根节点开始,通过比较索引键值逐层向下遍历,直到找到叶子节点中符合条件的索引项。 - 回表:如果查询所需的字段不完全包含在索引中,就需要回表操作。即根据索引叶子节点中的主键值,在聚簇索引(通常是主键索引,数据行按主键顺序存储)中查找完整的数据行。这是因为聚簇索引的叶子节点包含了完整的数据记录。回表操作会增加I/O开销,因为可能需要从不同的磁盘块读取数据。通过覆盖索引,可以减少甚至避免回表操作,直接从索引中获取所需数据,提高查询性能。
- 排序操作:在上述查询中,
ORDER BY order_time DESC
涉及排序操作。如果索引结构中包含order_time
字段(如idx_user_id_status_time
),MySQL可以利用索引的有序性直接进行排序,避免额外的排序操作(如文件排序)。如果索引中不包含order_time
字段,MySQL可能需要将符合条件的数据读取到内存中,然后进行排序,这会增加内存和CPU的开销。