MST

星途 面试题库

面试题:MySQL子查询优化:递归与分层场景

假设有一个自关联表`organization`,用于表示公司的组织架构,表结构为(`employee_id`、`employee_name`、`manager_id`),其中`manager_id`指向其上级领导的`employee_id`。现在需要查询出每个部门(以顶层领导划分部门)中入职时间最早的员工信息。请用子查询实现,并详细说明在大数据量下如何对该子查询进行深度优化,包括索引设计、查询改写等方面。
36.0万 热度难度
数据库MySQL

知识考点

AI 面试

面试题答案

一键面试

查询语句

SELECT sub.employee_id, sub.employee_name, sub.manager_id, sub.hire_date
FROM (
    SELECT 
        o.employee_id, 
        o.employee_name, 
        o.manager_id, 
        o.hire_date,
        ROW_NUMBER() OVER (PARTITION BY top_manager.employee_id ORDER BY o.hire_date) AS rn
    FROM 
        organization o
    JOIN 
        (
            SELECT 
                employee_id 
            FROM 
                organization 
            WHERE 
                manager_id IS NULL
        ) top_manager
    ON 
        o.manager_id = top_manager.employee_id OR o.employee_id = top_manager.employee_id
) sub
WHERE sub.rn = 1;

解释

  1. 首先通过子查询top_manager找到所有顶层领导(即manager_idNULL的员工)。
  2. 然后将organization表与top_manager子查询进行连接,连接条件是当前员工的manager_id等于顶层领导的employee_id或者当前员工本身就是顶层领导。
  3. 使用ROW_NUMBER()窗口函数,按照每个顶层领导分区,并根据hire_date(假设表中有这个字段,题目未提及,这里假设为入职时间字段)进行排序,为每个分区内的员工生成一个序号。
  4. 最后在最外层查询中选择序号为1的员工,即每个部门中入职时间最早的员工。

大数据量下的优化

索引设计

  1. 单列索引
    • organization表的manager_id字段上创建索引,因为连接条件中使用到了这个字段,在大数据量下,索引可以加快连接的速度。
    CREATE INDEX idx_manager_id ON organization(manager_id);
    
    • 如果hire_date字段没有索引,也创建一个索引,以加快ROW_NUMBER()函数中排序操作的速度。
    CREATE INDEX idx_hire_date ON organization(hire_date);
    
  2. 复合索引
    • 考虑创建复合索引(manager_id, hire_date),这样在连接和排序时可以利用到该复合索引,提高查询效率。
    CREATE INDEX idx_manager_hire ON organization(manager_id, hire_date);
    

查询改写

  1. CTE(公共表表达式)
    • 将顶层领导的查询部分使用CTE来改写,使查询结构更清晰,并且某些数据库系统对CTE有更好的优化策略。
    WITH top_manager AS (
        SELECT 
            employee_id 
        FROM 
            organization 
        WHERE 
            manager_id IS NULL
    )
    SELECT sub.employee_id, sub.employee_name, sub.manager_id, sub.hire_date
    FROM (
        SELECT 
            o.employee_id, 
            o.employee_name, 
            o.manager_id, 
            o.hire_date,
            ROW_NUMBER() OVER (PARTITION BY top_manager.employee_id ORDER BY o.hire_date) AS rn
        FROM 
            organization o
        JOIN 
            top_manager
        ON 
            o.manager_id = top_manager.employee_id OR o.employee_id = top_manager.employee_id
    ) sub
    WHERE sub.rn = 1;
    
  2. 避免笛卡尔积
    • 在大数据量下,确保连接条件尽可能准确,避免产生笛卡尔积。这里的连接条件已经相对准确,但在复杂查询场景下,要注意这一点。
  3. 分区表
    • 如果数据量非常大,可以考虑对organization表进行分区,例如按照入职时间分区,这样在查询时可以减少扫描的数据量。具体的分区方式和实现依赖于所使用的数据库系统。例如在MySQL中,可以使用RANGE分区:
    CREATE TABLE organization (
        employee_id INT,
        employee_name VARCHAR(255),
        manager_id INT,
        hire_date DATE
    )
    PARTITION BY RANGE (YEAR(hire_date)) (
        PARTITION p0 VALUES LESS THAN (2010),
        PARTITION p1 VALUES LESS THAN (2015),
        PARTITION p2 VALUES LESS THAN (2020),
        PARTITION p3 VALUES LESS THAN (2025),
        PARTITION p4 VALUES LESS THAN (MAXVALUE)
    );
    

这样在查询入职时间最早的员工时,数据库可以根据分区快速定位到相应的数据。