面试题答案
一键面试查看查询执行计划
在MySQL Query Browser中,在查询语句前加上 EXPLAIN
关键字,然后执行该查询,执行结果会展示查询的执行计划。例如:
EXPLAIN SELECT * FROM table1 JOIN table2 ON table1.id = table2.id WHERE table1.column = 'value';
执行上述查询后,会显示如下信息(以表格形式呈现,不同版本可能字段有差异):
- id:查询中每个
SELECT
子句的标识符,id相同表示查询是在同一层次,id不同表示子查询。 - select_type:
SELECT
类型,常见有SIMPLE
(简单查询,不包含子查询和联合查询)、PRIMARY
(最外层的查询)、SUBQUERY
(子查询)等。 - table:输出的行数据来自哪张表。
- partitions:匹配的分区。
- type:连接类型,如
ALL
(全表扫描)、index
(索引扫描)、range
(范围扫描)、ref
(使用非唯一索引扫描)等,ALL
性能最差,应尽量避免。 - possible_keys:可能使用到的索引。
- key:实际使用到的索引。
- key_len:索引使用的字节数,可评估索引使用效率。
- ref:显示使用哪个列或常数与索引进行比较。
- rows:预估扫描的行数,行数越少通常性能越好。
- filtered:预估条件过滤后返回的行百分比。
分析可能存在的性能问题
- 全表扫描(type为ALL):如果某个表的连接类型为
ALL
,表示对该表进行全表扫描,这在数据量较大时性能较差,应考虑添加合适的索引。 - 未使用索引(key为NULL):
possible_keys
有索引,但key
为NULL
,说明查询未使用预期的索引,可能需要调整查询语句或索引结构。 - 过多的临时表和文件排序:如果执行计划中出现
Using temporary
和Using filesort
,表示MySQL需要创建临时表或进行文件排序,这会影响性能。
优化查询的方法及操作流程
- 添加索引:
- 分析:根据执行计划中显示的未使用索引的情况,添加合适的索引。例如,如果
type
为ALL
且possible_keys
为空,针对WHERE
子句中的条件列添加索引。 - 操作流程:在MySQL Query Browser中使用
CREATE INDEX
语句创建索引。例如,给table1
表的column
列添加普通索引:
- 分析:根据执行计划中显示的未使用索引的情况,添加合适的索引。例如,如果
CREATE INDEX idx_column ON table1(column);
- 优化子查询:
- 分析:子查询可能导致多次扫描数据库,可以将子查询改写为连接查询,减少查询次数。例如,子查询
SELECT column FROM table2 WHERE id IN (SELECT id FROM table1 WHERE condition)
,可改写为连接查询SELECT table2.column FROM table1 JOIN table2 ON table1.id = table2.id WHERE table1.condition
。 - 操作流程:直接在查询语句中改写子查询为连接查询形式,然后重新执行并查看执行计划,确认性能是否提升。
- 分析:子查询可能导致多次扫描数据库,可以将子查询改写为连接查询,减少查询次数。例如,子查询
- 使用覆盖索引:
- 分析:如果查询只需要索引列的值,覆盖索引可以避免回表操作,提高性能。例如,查询
SELECT column1, column2 FROM table1 WHERE condition
,若column1
和column2
已包含在某个索引中,则该索引为覆盖索引。 - 操作流程:通过
CREATE INDEX
创建复合索引,将查询涉及的列都包含在索引中。例如:
- 分析:如果查询只需要索引列的值,覆盖索引可以避免回表操作,提高性能。例如,查询
CREATE INDEX idx_columns ON table1(column1, column2);
- 调整连接顺序:
- 分析:不同的表连接顺序可能会影响查询性能,MySQL Query Optimizer会尝试选择最优顺序,但有时手动调整可能更好。通常将数据量小的表放在前面连接。
- 操作流程:直接在查询语句中调整
JOIN
子句中表的顺序,重新执行查询并查看执行计划,对比性能。
- 分区表:
- 分析:如果表数据量非常大,可以考虑对表进行分区,将数据分散存储,减少单次查询的数据量。例如,按日期对日志表进行分区。
- 操作流程:使用
CREATE TABLE
语句创建分区表。例如,按日期范围分区:
CREATE TABLE log_table (
id INT,
log_date DATE,
log_content TEXT
)
PARTITION BY RANGE (YEAR(log_date)) (
PARTITION p0 VALUES LESS THAN (2020),
PARTITION p1 VALUES LESS THAN (2021),
PARTITION p2 VALUES LESS THAN (2022)
);
然后将原表数据导入到分区表,并调整查询语句查询分区表。