MST

星途 面试题库

面试题:MySQL复杂子查询的优化及替代方案

现有三个表,`products`(`product_id`, `product_name`, `category_id`)存储产品信息,`categories`(`category_id`, `category_name`)存储产品类别信息,`orders_items`(`order_item_id`, `order_id`, `product_id`, `quantity`)存储订单商品项信息。需求是找出每个类别中订单数量最多的产品名称。请先用子查询实现,然后说明该子查询在性能方面可能存在的问题,并给出至少两种替代优化方案。
21.5万 热度难度
数据库MySQL

知识考点

AI 面试

面试题答案

一键面试

子查询实现

SELECT sub.category_name, sub.product_name
FROM (
    SELECT 
        c.category_name, 
        p.product_name, 
        SUM(oi.quantity) AS total_quantity,
        ROW_NUMBER() OVER (PARTITION BY c.category_id ORDER BY SUM(oi.quantity) DESC) AS rn
    FROM 
        products p
    JOIN 
        categories c ON p.category_id = c.category_id
    JOIN 
        orders_items oi ON p.product_id = oi.product_id
    GROUP BY 
        c.category_id, c.category_name, p.product_id, p.product_name
) sub
WHERE sub.rn = 1;

性能问题

  1. 嵌套查询的开销:子查询通常需要先执行子查询部分,再执行外部查询,这种嵌套结构增加了查询解析和执行的复杂度,特别是在大数据量下,开销较大。
  2. 多次扫描数据:在子查询和外部查询中,可能会对相同的表进行多次扫描,导致I/O开销增加。
  3. 聚合和排序操作:在子查询中使用GROUP BYROW_NUMBER()函数进行聚合和排序,这对于大数据量的表操作是比较耗时的。

替代优化方案

方案一:使用公共表达式(CTE)

WITH category_product_quantity AS (
    SELECT 
        c.category_id,
        c.category_name, 
        p.product_id,
        p.product_name, 
        SUM(oi.quantity) AS total_quantity
    FROM 
        products p
    JOIN 
        categories c ON p.category_id = c.category_id
    JOIN 
        orders_items oi ON p.product_id = oi.product_id
    GROUP BY 
        c.category_id, c.category_name, p.product_id, p.product_name
),
ranked_products AS (
    SELECT 
        category_id,
        category_name,
        product_name,
        total_quantity,
        ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY total_quantity DESC) AS rn
    FROM 
        category_product_quantity
)
SELECT 
    category_name,
    product_name
FROM 
    ranked_products
WHERE 
    rn = 1;

CTE 使得查询逻辑更加清晰,并且数据库优化器有时能更好地处理CTE,可能会减少重复扫描数据的问题。

方案二:使用临时表

-- 创建临时表存储每个产品的总订单数量
CREATE TEMPORARY TABLE temp_product_quantity AS
SELECT 
    c.category_id,
    c.category_name, 
    p.product_id,
    p.product_name, 
    SUM(oi.quantity) AS total_quantity
FROM 
    products p
JOIN 
    categories c ON p.category_id = c.category_id
JOIN 
    orders_items oi ON p.product_id = oi.product_id
GROUP BY 
    c.category_id, c.category_name, p.product_id, p.product_name;

-- 在临时表上进行排序和筛选
SELECT 
    category_name,
    product_name
FROM (
    SELECT 
        category_id,
        category_name,
        product_name,
        total_quantity,
        ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY total_quantity DESC) AS rn
    FROM 
        temp_product_quantity
) sub
WHERE 
    rn = 1;

-- 删除临时表
DROP TEMPORARY TABLE temp_product_quantity;

临时表可以将中间结果存储起来,减少重复计算,在一定程度上提高查询性能,尤其是对于复杂的查询。