分页、CTE 与结果集运算

共 40 题
📑 题目列表 40 题
#
★★★

1. LIMIT/OFFSET 的语义,跳过 N 行取 M 行的代价?

说明 LIMIT/OFFSET 的语义与代价?

  • OFFSET 跳过
  • LIMIT 取行
  • 代价

LIMIT M OFFSET N 表示跳过前 N 行,取接下来的 M 行。代价:数据库仍需扫描并丢弃前 N 行(除非索引),OFFSET 越大代价越高(深分页)。

OFFSET 深分页是性能陷阱,需 keyset 分页。

#
★★★

2. MySQL 中 LIMIT 的优化,优化器在大 LIMIT 时可能选择全表扫描?

说明 MySQL 中 LIMIT 的优化?

  • 大 LIMIT
  • 全表扫描
  • 优化器

当 LIMIT 极大(如 LIMIT 1000000)时,优化器可能认为全表扫描比走索引更快(因为索引+回表 vs 全表顺序扫描),从而选择全表扫描。小 LIMIT 倾向索引。

大 LIMIT 改变优化器选择,影响深分页性能。

#
★★★

3. PostgreSQL 中 OFFSET 的实现,执行器仍需扫描所有跳过的行?

说明 PostgreSQL 中 OFFSET 的实现?

  • OFFSET 扫描
  • 跳过行
  • 执行器

PostgreSQL 的 OFFSET 在执行器层面仍会扫描并丢弃所有跳过的行(不返回客户端),因此 OFFSET 大时仍消耗扫描代价。优化器无法跳过,除非用 keyset 谓词。

OFFSET 只是"丢弃返回",扫描成本仍在。

#
★★★

4. SQL Server 的 OFFSET ... FETCH NEXT 的语法?

说明 SQL Server 的 OFFSET ... FETCH NEXT?

  • OFFSET
  • FETCH NEXT
  • 分页

SQL Server 用 OFFSET...FETCH NEXT 实现分页:ORDER BY ... OFFSET N ROWS FETCH NEXT M ROWS ONLY。需配 ORDER BY,是 SQL 标准分页语法。

SQL Server 用标准 OFFSET...FETCH NEXT 分页,需配 ORDER BY。

SELECT * FROM t ORDER BY id OFFSET 10 ROWS FETCH NEXT 5 ROWS ONLY;
#
★★★

5. Seek 分页(Keyset Pagination)的实现,WHERE (created_at, id) > (?, ?) ORDER BY created_at, id LIMIT ?

说明 Keyset 分页的实现?

  • Keyset 分页
  • 游标条件
  • 行值比较

Keyset 分页用 WHERE (col1, col2) > (last_col1, last_col2) 记录上次位置,配合 ORDER BY 和 LIMIT 取下一页,避免 OFFSET 扫描。利用复合索引 (col1, col2) 高效定位。

Keyset 用行值比较定位下一页,避免 OFFSET 深扫描。

SELECT * FROM t
WHERE (created_at, id) > (?last_created, ?last_id)
ORDER BY created_at, id
LIMIT 20;
#
★★★

6. 深分页的优化方案,基于游标、基于 ID 范围、基于二级索引?

说明深分页的优化方案:游标(keyset)分页、ID 范围分页、二级索引延迟关联各自如何避免 OFFSET 大偏移扫描?

  • 游标分页
  • ID 范围
  • 二级索引

深分页优化:基于游标(keyset 分页,记录上次位置)、基于 ID 范围(WHERE id > last_id)、基于二级索引(延迟关联,先取主键再回表)。避免 OFFSET 大偏移扫描。

三种方案都避免 OFFSET 逐行跳过:keyset 记录上次位置、ID 范围天然有序、延迟关联先取主键再回表;答题核心是把大偏移转化为条件过滤,并说明 keyset 分页要求排序列唯一确定顺序。

#
★★★

7. 深分页(Deep Pagination)的性能问题,OFFSET 1000000 时的全表扫描?

说明深分页(Deep Pagination)的性能问题:OFFSET 很大时为什么慢,即使走索引代价为何依然很高?

  • OFFSET 大
  • 全表扫描
  • 性能

OFFSET 1000000 时数据库需扫描并丢弃前 100 万行,即使走索引也需大量回表,代价高。深分页导致性能急剧下降,需用 keyset/游标分页。

深分页是经典性能问题,OFFSET 越大越慢。

#
★★★

8. MySQL 中 LIMIT 与索引的协同?

说明 MySQL 中 LIMIT 与索引的协同?

  • 索引排序
  • LIMIT
  • 覆盖

LIMIT 配合 ORDER BY 命中索引时,可用索引有序扫描直接取前 N 行,避免排序。覆盖索引可减少回表。但大 OFFSET 仍会扫描跳过行。

索引 + LIMIT 是高效分页的基础。

#
★★★

9. 延迟关联(Deferred Join)的深分页优化?

说明延迟关联的深分页优化?

  • 延迟关联
  • 先主键后回表
  • 优化

延迟关联(Deferred Join):先按索引取页的主键(LIMIT/OFFSET 在索引上),再回表关联取完整行,避免大 OFFSET 时的大量回表。减少扫描数据量。

先在索引上取主键页,再回表取完整行,减少回表量。

SELECT t.* FROM t
JOIN (SELECT id FROM t WHERE ... ORDER BY id LIMIT 1000000, 20) tmp
ON t.id = tmp.id;
#
★★★

10. LIMIT 10 OFFSET 20 的语义?

说明 LIMIT 10 OFFSET 20 的语义?

  • OFFSET 20
  • LIMIT 10
  • 语义

LIMIT 10 OFFSET 20 跳过前 20 行,返回接下来的 10 行(即第 21-30 行)。先 OFFSET 后 LIMIT。

先跳过 20 行再取 10 行,即第 21-30 行。

#
★★★

11. PageHelper 等分页插件的实现原理?

说明 PageHelper 分页插件的原理?

  • 拦截 SQL
  • 自动分页
  • 原理

PageHelper 等分页插件通过拦截 SQL,自动改写为带 LIMIT(MySQL)/ROWNUM(Oracle)的分页语句,并自动执行 count 查询统计总数。基于 MyBatis 拦截器实现。

分页插件基于 ORM 拦截器机制,在 SQL 执行前自动改写为 LIMIT/ROWNUM 分页语句并附加 count 统计;理解拦截-改写-计数三步流程,才能解释插件为何透明,也能分析其性能隐患。

#
★★★

12. 分页缓存(Page Cache)的设计?

说明分页缓存(Page Cache)的设计思路:缓存常用页可减少查询,但如何保证缓存失效与数据一致性?

  • 缓存
  • 分页
  • 一致性

分页缓存可缓存常用页结果(如 Redis 缓存前几页),减少数据库查询。但需处理缓存失效与数据一致性(数据变化时缓存过期)。适合高频稳定数据。

分页缓存以数据一致性为代价换取查询加速,只适合高频且稳定的首页或前几页数据;缓存失效策略(TTL、主动失效)是设计核心,答题时强调缓存不是银弹,一致性方案必须同步设计。

#
★★★

13. 深分页的近似方案(next cursor)?

说明深分页的近似方案 next cursor:客户端如何记录上次位置并基于该键取下一页,避免 OFFSET 深度扫描?

  • next cursor
  • 游标
  • 近似

深分页近似方案用"next cursor"(记录上次位置游标)而非 OFFSET,客户端保存上次返回的最后一条记录的键,下次基于该键取下一页。避免 OFFSET 深度扫描。

next cursor 是 keyset 分页的实践形态。

#
★★★

14. UNION ALL 的去重由后续 SELECT DISTINCT 处理时的取舍?

说明 UNION ALL 与后续 DISTINCT 的取舍?

  • UNION ALL 不去重
  • DISTINCT 去重
  • 取舍

UNION ALL 保留重复行,不去重;若需去重可用 UNION(去重)或后续 SELECT DISTINCT。UNION ALL 更快(无去重开销),需要去重时权衡性能。

不需要去重时用 UNION ALL 更高效。

#
★★★

15. UNION 与 JOIN 的根本差异,纵向(行)vs 横向(列)?

说明 UNION 与 JOIN 的根本差异?

  • UNION 纵向
  • JOIN 横向
  • 差异

UNION 纵向拼接(行数相加,列数相同);JOIN 横向拼接(列数相加,按条件连接行)。UNION 合并结果集,JOIN 关联表。

UNION 是集合运算,JOIN 是表连接。

#
★★★

16. 集合运算的 ORDER BY 应用,只能在最后一个 SELECT 后写?

说明集合运算的 ORDER BY?

  • ORDER BY 位置
  • 最后 SELECT
  • 语法

UNION/INTERSECT/EXCEPT 的 ORDER BY 只能写在最后一个 SELECT 之后,对整个结果排序。不能在每个分支单独 ORDER BY(除非用子查询)。

ORDER BY 作用于整个合并结果,只能写在最后且按第一个 SELECT 列名。

SELECT a FROM t1 UNION SELECT b FROM t2 ORDER BY a;
#
★★

17. PostgreSQL 的 INTERSECT ALL、EXCEPT ALL 语义?

说明 INTERSECT ALL 与 EXCEPT ALL 语义?

  • ALL 保留重复
  • 语义
  • 差异

INTERSECT 去重后取交集;INTERSECT ALL 保留重复次数(取两集合中出现的较小次数)。EXCEPT 去重后取差集;EXCEPT ALL 保留差集重复次数。ALL 保留重复。

ALL 变体保留重复语义,与不带 ALL 的去重不同。

#
★★

18. PostgreSQL 中 INTERSECT 与 EXISTS 的等价?

说明 INTERSECT 与 EXISTS 的等价?

  • 等价
  • 转换
  • 语义

INTERSECT 与 EXISTS 在某场景等价(如找两表共同元素),但 INTERSECT 返回去重后的共同行,EXISTS 判断存在性。优化器可能将 INTERSECT 转为半连接/去重连接。

需要共同行的值用 INTERSECT,存在性用 EXISTS。

#
★★

19. UNION ALL 与 CTE 的性能对比?

说明 UNION ALL 与 CTE 的性能对比?

  • UNION ALL
  • CTE
  • 性能

UNION ALL 直接合并多个查询结果;CTE 可复用子查询(物化与否)。若 CTE 被多次引用且物化,可能比 UNION ALL 更高效(避免重复计算);否则 UNION ALL 开销小。

性能取决于 CTE 是否物化与复用次数。

#
★★

20. UNION 的列名采用第一个 SELECT 的列名?

说明 UNION 的列名规则?

  • 列名
  • 第一个 SELECT
  • UNION

UNION 结果集的列名采用第一个 SELECT 的列名(或别名),后续 SELECT 的列名被忽略。后续 ORDER BY 用第一个 SELECT 的列名。

UNION 结果集列名一律取第一个 SELECT 的列名,后续列名被静默忽略,ORDER BY 也须引用第一个 SELECT 的列名;这是常见陷阱,回答时点明以第一个 SELECT 为准即可。

#
★★

21. CTE 中能否 INSERT/UPDATE/DELETE(数据修改 CTE)?

说明 CTE 中的数据修改?

  • 数据修改 CTE
  • RETURNING
  • 支持

PostgreSQL 支持 CTE 中的数据修改(INSERT/UPDATE/DELETE),配合 RETURNING 供后续查询使用。MySQL 8.0 不支持 CTE 内 DML。这是方言差异。

PostgreSQL 支持 CTE 内 DML 并配合 RETURNING,MySQL 不支持。

WITH del AS (DELETE FROM t WHERE id=1 RETURNING *)
SELECT * FROM del;
#
★★

22. 多个 CTE 的链式依赖,WITH cte1 AS (...),cte2 AS (SELECT FROM cte1)?

说明多个 CTE 的链式依赖?

  • 链式依赖
  • 引用
  • 顺序

多个 CTE 可链式依赖:WITH cte1 AS (...), cte2 AS (SELECT ... FROM cte1) SELECT ...。后定义的 CTE 可引用前面定义的 CTE。

后定义 CTE 可引用前面 CTE,实现分层复用。

WITH cte1 AS (SELECT id FROM t1),
     cte2 AS (SELECT * FROM cte1 WHERE id > 10)
SELECT * FROM cte2;
#
★★

23. CTE 与临时视图(VIEW)的差异?

说明 CTE 与临时 VIEW 的差异?

  • CTE 语句内
  • VIEW 持久
  • 差异

CTE 在单条语句内有效,不持久化;视图(VIEW)是持久化的命名查询,可复用。CTE 适合一次性复用,VIEW 适合长期共享。

CTE 作用域是语句,VIEW 是数据库对象。

#
★★

24. CTE 在 OLAP 中的应用(多步聚合)?

说明 CTE 在 OLAP 的应用?

  • 多步聚合
  • 可读性
  • OLAP

CTE 在 OLAP 中用于多步聚合:先 CTE 预聚合,再在后续查询中复用,提高可读性与可维护性。避免嵌套子查询。

CTE 让复杂 OLAP 查询分层清晰。

#
★★

25. CTE 在 PostgreSQL 函数中的可见性?

说明 CTE 在函数中的可见性?

  • 函数内
  • 可见性
  • CTE

CTE 在 PostgreSQL 函数内作为 SQL 语句的一部分可见,其作用域为所在语句。函数内多条语句各自可有自己的 CTE,不能跨语句引用。

CTE 的作用域仅限所在单条 SQL 语句,函数内多条语句各自维护自己的 CTE,不能跨语句引用;理解作用域边界才能避免写出引用上一条语句 CTE 的错误代码,这也是 CTE 与临时表的关键区别之一。

#
★★

26. CTE 在 SQL Server 中的实现差异?

说明 CTE 在 SQL Server 的实现差异?

  • 非递归
  • 递归
  • 差异

SQL Server 的 CTE 支持非递归与递归(递归 CTE 用 WITH 自身引用)。CTE 在语句内有效,可被多次引用。SQL Server 的 CTE 默认不强制物化,优化器可内联。

递归 CTE 是 SQL Server 的常用特性。

#
★★

27. CTE 的 AS MATERIALIZED 与 AS NOT MATERIALIZED(PG 12+)?

说明 CTE 的物化控制?

  • MATERIALIZED
  • NOT MATERIALIZED
  • 物化

PostgreSQL 12+ 允许 AS MATERIALIZED(强制物化,缓存 CTE 结果)与 AS NOT MATERIALIZED(强制内联,避免物化)。默认优化器决定。物化适合多次引用,内联适合单次。

物化缓存结果适用多次引用,内联避免物化适用单次引用。

WITH cte AS MATERIALIZED (SELECT ...) SELECT ...
WITH cte AS NOT MATERIALIZED (SELECT ...) SELECT ...
#
★★

28. CTE 的别名能否在 SELECT 中引用?

说明 CTE 别名引用?

  • 别名
  • 引用
  • CTE

CTE 的别名(如 cte AS (...))可在主查询和后续 CTE 中作为表名引用,也可在 FROM 中引用。

CTE 名作为表名可在主查询与后续 CTE 中引用。

WITH cte AS (SELECT 1 AS x) SELECT x FROM cte;
#
★★

29. WITH ... INSERT ... 的数据修改 CTE?

说明 WITH ... INSERT 数据修改 CTE?

  • 数据修改 CTE
  • INSERT
  • 前置 WITH

语法为 WITH ... INSERT ... RETURNING,可先定义 CTE 再插入,或用 INSERT 配合 RETURNING 供后续操作。PostgreSQL 支持。

前置 WITH 定义源后插入,配合 RETURNING 返回插入行。

WITH src AS (SELECT id, val FROM staging)
INSERT INTO t (id, val) SELECT id, val FROM src RETURNING id;
#
★★

30. Oracle 的 ROWNUM 与 FETCH FIRST n ROWS ONLY 的差异?

说明 Oracle 的 ROWNUM 与 FETCH FIRST?

  • ROWNUM
  • FETCH FIRST
  • 差异

ROWNUM 是 Oracle 的伪列,在行返回时分配序号,需在 WHERE 中过滤(ROWNUM <= n);FETCH FIRST n ROWS ONLY 是标准分页语法,Oracle 12c+ 支持,更简洁。ROWNUM 限制(不能 ROWNUM > n 直接使用)。

ROWNUM 是伪列需在 WHERE 过滤,FETCH FIRST 是标准语法更简洁。

SELECT * FROM t WHERE ROWNUM <= 10;
SELECT * FROM t FETCH FIRST 10 ROWS ONLY;
#
★★

31. EXCEPT(MINUS)的语义,左表有但右表无的行?

说明 EXCEPT 的语义?

  • EXCEPT
  • 差集
  • 去重

EXCEPT 返回左集合有但右集合无的行(差集),默认去重。Oracle 用 MINUS 关键字。等价于 NOT IN/反连接(去重后)。

EXCEPT 返回左集合有而右集合无的行并默认去重,Oracle 中写作 MINUS;回答时与 INTERSECT、UNION 对比区分三种集合运算,并说明它等价于去重后的 NOT IN 或反连接。

#
★★

32. INTERSECT 的语义与实现,Hash Intersection、Sort-based Intersection?

说明 INTERSECT 的语义与实现?

  • 交集
  • Hash Intersection
  • Sort-based

INTERSECT 返回两集合共同的行(交集),默认去重。实现:Hash Intersection(哈希去重交集)或 Sort-based Intersection(排序后合并)。优化器按数据选择。

INTERSECT 返回两集合共同的行并默认去重,可用哈希或排序两种算法实现;与 EXCEPT 同为集合运算,答题时一并说明共同行加去重的语义,以及算法选择依赖数据量与内存。

#
★★

33. UNION 的列兼容性规则,列数必须相同,类型必须兼容?

说明 UNION 的列兼容性?

  • 列数
  • 类型
  • 兼容

UNION 的各 SELECT 列数必须相同,对应列类型必须兼容(可隐式转换)。不满足会报错。这是集合运算的硬性要求。

UNION 要求各 SELECT 列数相同且对应列类型可隐式转换,否则直接报错,这是集合运算的硬约束;回答时说明列数一致与类型兼容两个条件,并区分 UNION 与 UNION ALL 在去重上的差异。

#
★★

34. INTERSECT 与 DISTINCT 的差异?

说明 INTERSECT 与 DISTINCT 的差异?

  • INTERSECT 集合
  • DISTINCT 去重
  • 差异

INTERSECT 是集合运算,返回交集并去重;DISTINCT 是对单查询结果去重。INTERSECT 隐含去重,DISTINCT 是显式去重子句。两者概念不同。

INTERSECT 是集合运算,DISTINCT 是去重。

#

35. MySQL 8.0.31 之前没有原生 EXCEPT/INTERSECT,如何用 NOT IN/NOT EXISTS/LEFT JOIN 模拟?各有何 NULL 陷阱?

说明 MySQL 用 NOT IN 等模拟 EXCEPT/INTERSECT?

  • 模拟
  • NULL 陷阱
  • 反连接

MySQL 8.0.31 前无原生 EXCEPT/INTERSECT,可用 NOT IN/NOT EXISTS/LEFT JOIN 模拟差集,用 IN 模拟交集。陷阱:NOT IN 在子查询含 NULL 时结果为空(需排除 NULL);NOT EXISTS 不受影响。

模拟时注意 NULL 处理,优先用 NOT EXISTS。

#

36. LIMIT 0 的用途(仅返回结构)?

说明 LIMIT 0 的用途?

  • LIMIT 0
  • 返回结构
  • 用途

LIMIT 0 返回空结果但保留列结构,用于快速获取表结构(不取数据)。常用于元数据检查、测试。

LIMIT 0 返回空集但保留列结构,用于取表结构。

SELECT * FROM t LIMIT 0;
#

37. count(*) 总数与分页的取舍?

说明 count(*) 总数与分页的取舍?

  • count 总数
  • 代价
  • 取舍

分页常需 count(*) 总数,但大表 count 代价高,可能全表扫描。取舍:可缓存总数、近似估算、或去掉总数(无限滚动)。优化 count 用索引/覆盖。

大表 count(*) 可能全表扫描,代价高,与分页性能直接冲突;常用取舍是缓存总数、近似估算或去掉总数改为无限滚动,答题核心是识别 count 成本并用工程手段规避,而非盲目优化 SQL。

#

38. 分页查询的 ORDER BY 必要性?

说明分页查询的 ORDER BY 必要性?

  • 排序
  • 稳定性
  • 分页

分页必须有稳定的 ORDER BY,否则各页顺序不确定,产生重复/遗漏。最好加唯一键(如主键)作为末级排序保证稳定。

无 ORDER BY 的分页结果不稳定。

#

39. UNION ALL 在分库分表的应用?

说明 UNION ALL 在分库分表的应用?

  • 分库分表
  • UNION ALL
  • 合并

分库分表后,跨库/跨表查询可用 UNION ALL 合并各分片结果,再在应用层或外层处理。UNION ALL 不去重,适合合并分片数据。常用于全量扫描场景。

UNION ALL 合并分片结果,需注意排序与分页。

#

40. UNION 与 UNION ALL 中 NULL 的处理,UNION 去重时 NULL 如何合并,结果列类型推导的规则,跨库/异构数据源时 NULL 语义如何保持一致?

说明 UNION 中 NULL 的处理与类型推导?

  • NULL 合并
  • 类型推导
  • 跨库

UNION 去重时 NULL 视为相同值(多个 NULL 合并为一个);UNION ALL 保留多个 NULL。结果列类型由各 SELECT 的对应列类型推导(取兼容类型)。跨库/异构数据源时需保证 NULL 语义一致(如数据类型转换、NULL 表示一致)。

NULL 在去重时合并,类型取兼容类型。