SQL面试题:第二个优化案例

今天继续给大家分享一个 SQL 优化案例。

问题描述

已知表结构如下:

sql 复制代码
CREATE TABLE `customer` (
`C_CUSTKEY` int NOT NULL,
`C_NAME` varchar(25) NOT NULL,
`C_ADDRESS` varchar(40) NOT NULL,
`C_NATIONKEY` int NOT NULL,
`C_PHONE` char(15) NOT NULL,
`C_ACCTBAL` decimal(15,2) NOT NULL,
`C_MKTSEGMENT` char(10) NOT NULL,
`C_COMMENT` varchar(117) NOT NULL,
PRIMARY KEY (`C_CUSTKEY`)
) ENGINE=InnoDB;

CREATE TABLE `orders` (
`O_ORDERKEY` int NOT NULL,
`O_CUSTKEY` int NOT NULL,
`O_ORDERSTATUS` char(1) NOT NULL,
`O_TOTALPRICE` decimal(15,2) NOT NULL,
`O_ORDERDATE` date NOT NULL,
`O_ORDERPRIORITY` char(15) NOT NULL,
`O_CLERK` char(15) NOT NULL,
`O_SHIPPRIORITY` int NOT NULL,
`O_COMMENT` varchar(79) NOT NULL
PRIMARY KEY (`C_ORDERKEY`)
) ENGINE=InnoDB;

请问下面的查询语句应该如何优化?

sql 复制代码
SELECT o_custkey, c_name, sum(o.o_totalprice)
FROM customer c, orders o 
WHERE o_custkey = c_custkey 
GROUP BY o_custkey, c_name
ORDER BY c_name;

思路分析

首先,这个查询的作用是统计每个客户的订单金额汇总,数据来自 orders 表;最后按照客户名称排序,名称来自 customer 表,两个表通过 c_custkey 字段关联。看一下执行计划:

sql 复制代码
EXPLAIN
select c_custkey, c_name, sum(o.o_totalprice)
from customer c, orders o 
where o_custkey = c_custkey 
group by c_name, c_custkey
order by c_name;

id|select_type|table|partitions|type  |possible_keys|key    |key_len|ref           |rows|filtered|Extra                          |
--+-----------+-----+----------+------+-------------+-------+-------+--------------+----+--------+-------------------------------+
 1|SIMPLE     |o    |          |ALL   |             |       |       |              |   1|   100.0|Using temporary; Using filesort|
 1|SIMPLE     |c    |          |eq_ref|PRIMARY      |PRIMARY|4      |hr.o.O_CUSTKEY|   1|   100.0|                               |

orders 表使用了 ALL 全表扫描,由于 group by,所以 Extra 包含了临时表(Using temporary);由于 order by,所以 Extra 包含了排序操作(Using filesort)。这些都会导致性能问题。

第一步,考虑到每个客户的订单金额汇总只需要 orders 表中的数据,可以单独按照 o_custkey 分组,并且基于 o_custkey 和 o_totalprice 创建复合索引,利用索引覆盖扫描优化;然后再和 customer 表关联。另外,c_custkey 是主键,决定了 c_name,延迟关联可以避免 group by 中的 c_name。

sql 复制代码
-- 创建索引
CREATE INDEX idx_orders_custkey_totalprice ON orders(o_custkey, o_totalprice);

EXPLAIN
select o.o_custkey, c.c_name, o.sumprice
from
(select o_custkey, sum(o_totalprice) sumprice
from orders 
group by o_custkey) o, customer c
where o_custkey = c_custkey 
order by c_name;

id|select_type|table     |partitions|type |possible_keys                |key                          |key_len|ref           |rows|filtered|Extra         |
--+-----------+----------+----------+-----+-----------------------------+-----------------------------+-------+--------------+----+--------+--------------+
 1|PRIMARY    |c         |          |ALL  |PRIMARY                      |                             |       |              |   1|   100.0|Using filesort|
 1|PRIMARY    |<derived2>|          |ref  |<auto_key0>                  |<auto_key0>                  |4      |hr.c.C_CUSTKEY|   2|   100.0|              |
 2|DERIVED    |orders    |          |index|idx_orders_custkey_totalprice|idx_orders_custkey_totalprice|11     |              |   1|   100.0|Using index   |

子查询 o 已经优化完成,Extra 中的 Using index 显示了索引覆盖扫描。剩下的问题就是 customer 表上的排序操作,对应 Extra 中的 Using filesort。

第二步,基于 c_name 字段创建索引,避免排序操作。

sql 复制代码
-- 创建索引
CREATE INDEX idx_customer_name ON customer(c_name);

EXPLAIN
select c.c_custkey, c.c_name, o.sumprice
from
(select o_custkey, sum(o_totalprice) sumprice
from orders 
group by o_custkey) o, customer c
where o_custkey = c_custkey 
order by c_name;

id|select_type|table     |partitions|type |possible_keys                |key                          |key_len|ref           |rows|filtered|Extra      |
--+-----------+----------+----------+-----+-----------------------------+-----------------------------+-------+--------------+----+--------+-----------+
 1|PRIMARY    |c         |          |index|PRIMARY                      |idx_customer_name            |102    |              |   1|   100.0|Using index|
 1|PRIMARY    |<derived2>|          |ref  |<auto_key0>                  |<auto_key0>                  |4      |hr.c.C_CUSTKEY|   2|   100.0|           |
 2|DERIVED    |orders    |          |index|idx_orders_custkey_totalprice|idx_orders_custkey_totalprice|11     |              |   1|   100.0|Using index|

可以看到,对于 customer 表的访问,也使用了索引覆盖扫描,因为二级索引包含了主键字段(c_custkey)。

额外补充,还有一种写法就是调整 group by 字段的顺序,把 c_name 放在第一位,并且把 c_custkey 改成 o_custkey:

sql 复制代码
EXPLAIN 
select o_custkey, c_name, sum(o.o_totalprice)
from customer c, orders o 
where o_custkey = c_custkey 
group by c_name, o_custkey
order by c_name;

id|select_type|table|partitions|type |possible_keys                |key                          |key_len|ref           |rows|filtered|Extra      |
--+-----------+-----+----------+-----+-----------------------------+-----------------------------+-------+--------------+----+--------+-----------+
 1|SIMPLE     |c    |          |index|PRIMARY,idx_customer_name    |idx_customer_name            |102    |              |   1|   100.0|Using index|
 1|SIMPLE     |o    |          |ref  |idx_orders_custkey_totalprice|idx_orders_custkey_totalprice|4      |hr.c.C_CUSTKEY|   1|   100.0|Using index|

留个作业,为什么呢?

相关推荐
小米里的大麦2 分钟前
01 在 CentOS 7 中安装 MySQL
linux·mysql·centos
荒川之神2 分钟前
ORACLE 11G的审计
数据库·oracle
StackNoOverflow9 分钟前
MySQL 的性能调优(第一部分)
数据库·mysql
君穆南9 分钟前
MySQL备份脚本
数据库·mysql·adb
数据库知识分享者小北11 分钟前
告别后端上下文断层!体验用 PolarDB Supabase 助力 AI 原生 IDE 完成 VibeCoding领取试用及多重好礼
数据库·人工智能·阿里云·关系型数据库·polardb·vibecoding
ea4on30 分钟前
看完这篇,我才MySQL索引是这样理解的
数据库
鬼先生_sir33 分钟前
MySQL进阶基础:索引、视图、存储过程与常用函数
数据库·mysql
Nturmoils34 分钟前
实时决策时代,工业物联网需要什么样的数据库?
数据库·后端
Flying pigs~~35 分钟前
RAG前身:基于mysql➕redis➕bm25的传统QA问答系统
数据库·redis·缓存·大模型·qa·rag·prompt提示词
fly spider36 分钟前
MySQL数据存储详解
数据库·mysql