跳转至

4. 2025题目四:查询优化

前置题目查询执行

题目种类:基础功能

代码框架:https://gitlab.eduxiji.net/csc1/csc-db/db2025/-/tree/main/rmdb

** **

题目描述:

查询优化是数据库系统中的重要组成部分,其目标是在保证查询结果正确性的前提下,通过改写 SQL 语句或调整执行计划,减少中间数据量、降低计算开销,从而提高查询执行效率。本题要求实现一个简化版的查询优化器,能够生成规范的查询计划树。

具体来说,我们要求实现以下三种优化:

  1. 基于规则的选择运算下推(谓词下推)。选择运算下推要求在查询计划树中,利用选择运算与投影运算,以及选择运算与笛卡尔积的交换律等,通过交换节点在树中的位置,尽可能将选择运算对应的节点放到查询计划树的底层。其目的是:尽可能早地过滤掉与后续操作无关的行,避免中间结果生成后再进行过滤所带来的额外开销。选择运算下推有助于减少中间数据中不必要的行,降低中间数据的传递和处理量,从而提高查询效率。
  2. 基于规则的投影运算下推。同上,投影运算下推要求在查询计划树中,利用投影运算与笛卡尔积的分配率,以及投影的串接定律等,通过交换节点在树中的位置,尽可能地先执行查询中的投影操作。其目的是:尽可能早地去除与后续操作无关的列,避免中间结果生成后再进行投影所带来的额外开销。投影运算下推有助于减少中间数据中不必要的列,可以显著降低数据传输和处理的开销。
  3. 基于简单代价估计的连接顺序优化。连接顺序优化是指在涉及多表连接的查询中,通过调整表的连接顺序,减小中间结果的大小,以降低查询的执行代价。本题要求实现以下连接顺序优化策略:

a. 采用左深树结构组织多表连接

b. 使用贪心算法选择连接顺序,具体规则如下:

- 首先选择基数最小的两个表作为初始连接

- 之后每次从剩余表中选择一个表加入,使得连接后的中间结果基数最小

- 表的基数指表中的行数,通过存储模块获取,需自行设计

c. 在连接顺序确定后,仍需应用选择运算下推和投影下推规则进行优化

对于本题,我们默认采用嵌套循环连接算法(Nested Loop Join),并假设数据均匀分布。

要求:在现有SQL查询语句的基础上,增加对EXPLAIN关键字的支持,基于下列所述的规范格式,显示优化后的查询计划树内容。题目的输入为EXPLAIN + SQL查询语句,输出需要采用树状结构展示SQL查询计划,利用缩进 (在输出中为\t) 表示节点层级关系,每个节点包括算子类型和相关属性。

本题目共需实现四种查询计划节点:

- Scan:表扫描节点;- Filter:过滤节点(对应选择运算);- Project:投影节点;- Join:连接节点

具体算子规范

节点类型 格式 备注
Scan节点 Scan(table=表名) 即叶节点
Filter节点 Filter(condition=[条件1,条件2,...]) 1.确保谓词的左值是表的列,多个条件按字典序排序2.条件中的列名必须包含表名前缀(多表查询时)
Project节点 Project(columns=[表名1.列名1,表名2.列名2,...]) 1.列名按字母顺序排序2.多表查询时使用表名前缀3.需要保留全部的列则输出Project(columns=[*])4.对于select语句根节点一定是Project节点
Join节点 Join(tables=[表名1,表名2,...], condition=[条件1,条件2,...]) 1.表名按字母顺序排序,表名为此节点下所有表的集合2.连接条件按字典序排序

同一层级下的多个节点,按照字典升序输出,即先按照Filter Join Project Scan的顺序输出,同类型的节点,Scan节点和Join节点按照table升序输出,Filter按照条件的字典序升序输出,Project节点按照“表名.列名”升序输出。所有的列输出时均要先输出各自的表名,如果表有别名,则使用别名。Condition的输出格式和原始的SQL语句保持一致。

测试示例:

输入:

EXPLAIN SELECT a, b FROM t WHERE a > 1 AND b < 10;

输出:

Project(columns=[t.a,t.b])

Filter(condition=[t.a>1,t.b<10])

​ Scan(table=t)

测试点1:选择运算下推

测试示例:

CREATE TABLE orders (

order_id int,

customer_id int,

order_date char(40),

total_amount float

);

CREATE TABLE customers (

customer_id int,

name char(50),

email char(100),

address char(200)

);

EXPLAIN SELECT * FROM customers c

JOIN orders o ON c.customer_id = o.customer_id

WHERE o.total_amount > 1000;

期望输出:

Project(columns=[*])

Join(tables=[customers,orders],condition=[c.customer_id=o.customer_id])

​ Filter(condition=[o.total_amount>1000])

​ Scan(table=orders)

Scan(table=customers)

测试点2:投影下推

测试示例:

CREATE TABLE orders (

order_id int,

customer_id int,

order_date char(40),

total_amount float

);

CREATE TABLE customers (

customer_id int,

name char(50),

email char(100),

address char(200)

);

EXPLAIN SELECT c.name, o.order_id

FROM customers c

JOIN orders o ON c.customer_id = o.customer_id;

期望输出:

Project(columns=[c.name,o.order_id])

Join(tables=[customers,orders],condition=[c.customer_id=o.customer_id])

​ Project(columns=[c.customer_id,c.name])

​ Scan(table=customers)

​ Project(columns=[o.customer_id,o.order_id])

​ Scan(table=orders)

测试点3:连接顺序优化(在进行连接顺序优化后,需要支持选择运算下推和投影下推)

测试示例:

CREATE TABLE orders (

order_id int,

customer_id int,

order_date char(40),

total_amount float

);

CREATE TABLE products (

product_id int,

name char(50),

price float

);

CREATE TABLE order_items (

order_id int,

product_id int,

quantity int

);

注:此处省略表插入的完整过程,插入后的metadata如下

customers: 50 行记录

orders: 200 行记录

order_items: 1000 行记录

products: 20000 行记录

EXPLAIN SELECT * FROM customers c

JOIN orders o ON c.customer_id = o.customer_id

JOIN order_items oi ON o.order_id = oi.order_id

JOIN products p ON oi.product_id = p.product_id

WHERE o.total_amount > 200;

期望输出:

由于采用嵌套循环连接算法并假设数据均匀分布,选择根据表的条目从低到高进行连接。确定连接顺序后应用规则选择运算下推。

Project(columns=[*])

Join(tables=[customers,order_items,orders,products],condition=[oi.product_id=p.product_id])

​ Join(tables=[customers,order_items,orders],condition=[o.order_id=oi.order_id])

​ Join(tables=[customers,orders],condition=[c.customer_id=o.customer_id])

​ Filter(condition=[o.total_amount>200])

​ Scan(table=orders)

​ Scan(table=customers)

​ Scan(table=order_items)

​ Scan(table=products)