数据类型、视图与函数

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

1. ER 模型(Entity-Relationship Model)中的实体、属性、联系、标识符在 Chen 记法与 Crow's Foot 记法中的表示差异是什么?

ER 模型(Entity-Relationship Model)中的实体、属性、联系、标识符在 Chen 记法与 Crow's Foot 记法中的表示差异是什么?

  • 两种记法的图形符号
  • 实体、属性、联系的表达差异
  • 基数的表示方式

Chen 记法(陈品山 1976 年提出)用矩形表示实体、椭圆表示属性、菱形表示联系,主键属性加下划线、多值属性双椭圆、派生属性虚线椭圆,联系用 1:N、M:N、1:1 标注在连线两侧,二元/三元联系都清晰可画;Crow's Foot 记法(信息工程风格)用矩形表示实体(属性直接写在矩形内,主键加 PK/下划线标注)、不使用菱形——联系由连线表达,靠近实体的"脚"符号表示基数(圆 O 表示零、单竖线表示一、鸡爪三叉表示多),配合方括号表示"至少一个"(O 圈+竖线表示 0..1、竖线+鸡爪表示 1..N 等),用连线上的标记组合表达最小/最大基数。

差异核心:Chen 记法把"联系"作为独立图形元素(菱形),适合表达复杂语义(属性化联系、多对多、三元联系)与教学理论;Crow's Foot 更贴近数据库表设计(属性内嵌、基数直接可读、容易转成物理模型),是工程工具(PowerDesigner、ER/Studio、dbdiagram.io)的主流。标识符表达:Chen 用下划线标注键属性(部分键用虚线),Crow's Foot 用 PK/FK 标记;弱实体在 Chen 中双矩形,Crow's Foot 中用"全参与+部分键"表示。选择依据:学术/复杂语义用 Chen,工程建模用 Crow's Foot。

答题先分述两种记法的符号体系(Chen 的矩形/椭圆/菱形与下划线键,Crow's Foot 的内嵌属性与鸡爪基数符号),再对比差异核心(联系独立元素 vs 连线表达、理论 vs 工程导向)与标识符表达,最后给选型建议。

#
★★★

2. 基数(Cardinality)的三种语义(min-max、look-here、look-across)在 ER 工具(PowerDesigner、ER/Studio、dbdiagram.io)中的实现差异如何?

基数的三种语义(min-max、look-here、look-across)在 ER 工具(PowerDesigner、ER/Studio、dbdiagram.io)中的实现差异如何?

  • 三种基数语义的定义
  • 工具默认语义差异
  • 建模歧义的规避

三种语义:min-max 计数——直接在联系两端标注"最少/最多参与数"(如 0..*、1..1),最精确、无歧义;look-here 语义——在"联系所在的那一侧"读取基数(如实体 A 侧标注表示"一个 A 关联多少个 B",需结合关系方向理解);look-across 语义——在"对侧"读取基数(标注"一个 B 关联多少个 A"),即与联系方向相反的读法。三种语义的差别本质是"标注读在联系的哪一端"。

工具实现:PowerDesigner 提供 "One-One/Many" 的显式基数配置(可定义 min/max cardinality 与 Dominant role),默认类似 look-here 逻辑建模,物理层生成外键时再转换;ER/Studio 以 "One-to-Many" 文字+符号表达,基数读法按 look-across 惯例(靠近子表的符号描述"父对子的数量关系");dbdiagram.io(纯文本 DSL)用 "||--o{" 等记号:符号靠近哪端就描述哪端的参与度(如 }o--|| 表示左侧 0..1、右侧 1..1),本质是 look-across(对端计数)。差异后果:同一模型在不同工具打开可能显示相反基数(工具按自己的语义渲染),跨工具导出(ERwin→PowerDesigner→dbdiagram)时基数可能翻转。 规避建议:建模前明确工具的基数语义(读文档确认是 min-max 还是方向性计数);关键联系用 min-max 显式标注(0..1、1..*)并写注释;用一致性检查(工具的内置验证)与评审兜底;物理 DDL(外键与唯一约束)是最终语义真相,以生成的 SQL 为准。

答题先定义三种基数语义(min-max 计数、look-here、look-across 的方向性读法),再分别讲三个工具的默认实现与导出翻转风险,最后给 min-max 显式标注与以 DDL 为准的规避建议。

#
★★★

3. 金融金额场景为什么必须用 DECIMAL/NUMERIC 而非 FLOAT/DOUBLE?二者在存储代价、运算性能与聚合精度累积误差上有何差异?

金融金额场景为什么必须用 DECIMAL/NUMERIC 而非 FLOAT/DOUBLE?二者在存储代价、运算性能与聚合精度累积误差上有何差异?

  • 二进制浮点的表示误差
  • DECIMAL 的十进制精确语义
  • 存储、性能与累积误差对比

根本原因:FLOAT/DOUBLE 是二进制浮点数,无法精确表示多数十进制小数(如 0.1 在二进制中是无限循环),存储与运算都产生舍入误差,且误差随运算累积;金融金额的相等比较、对账、报表加总要求精确的十进制语义,0.1 + 0.2 必须等于 0.3,浮点做不到。DECIMAL/NUMERIC(定点十进制)按十进制数位存储(每若干位一组存整数),精确表示小数并保证"同精度十进制运算结果精确",是金额、税率、余额的唯一正确类型。

差异量化:存储代价——DECIMAL 每 9 位十进制约 4 字节(PostgreSQL numeric 可变长),比 8 字节 double 略大但可控;运算性能——DECIMAL 运算(按十进制位运算)比硬件浮点慢数倍到数十倍,高频大表聚合(SUM/AVG 百万行)有明显 CPU 差距;精度累积误差——浮点 SUM 误差随行数线性累积(对账差一分钱),DECIMAL SUM 精确(除非超出标度被舍入,可扩大标度规避)。工程结论:金额一律 DECIMAL(p,s)(如 DECIMAL(18,4)),绝不用 FLOAT/DOUBLE;性能敏感且只做展示的指标可用浮点,但任何比较/加总/存储业务值必须定点;跨库(PG numeric、MySQL DECIMAL、SQL Server DECIMAL)语义一致。

答题先讲二进制浮点的表示误差原理与金融相等比较的需求,再量化对比存储(定点略大)、性能(定点慢)与累积误差(浮点线性累积)三项差异,最后给"金额用 DECIMAL、指标可用浮点"的工程结论。

#
★★★

4. VARCHAR(n) 与 TEXT 在 PostgreSQL 中是否真的完全等价(存储、TOAST、索引)?在 MySQL 中有何差异(行大小 65535 字节限制、索引需指定前缀长度、默认值限制)?

VARCHAR(n) 与 TEXT 在 PostgreSQL 中是否真的完全等价(存储、TOAST、索引)?在 MySQL 中有何差异(行大小限制、索引前缀、默认值限制)?

  • PostgreSQL 中 VARCHAR/TEXT 的等价性与 TOAST
  • MySQL 中 VARCHAR 与 TEXT 的差异
  • 行大小、索引前缀与默认值限制

PostgreSQL 中 VARCHAR(n)(限长)、VARCHAR(不限长)、TEXT 在底层完全等价:同一 varlena 存储格式(变长 + 长度前缀),无长度时行为一致,TOAST 机制相同(超 2KB 溢出到 TOAST 表),索引能力相同(B-tree 索引长度限制相同,长字符串走前缀/哈希索引),唯一差异是 VARCHAR(n) 强制长度约束而 TEXT 不强制。因此 PG 中"类型选择"只影响约束语义,不影响存储与性能;用 TEXT + CHECK (char_length(col) <= n) 可完全替代 VARCHAR(n)(官方也推荐 TEXT 或显式约束)。

MySQL 差异明显:其一,行大小限制——InnoDB 行最大 65535 字节(所有 VARCHAR 列合计计入),TEXT/BLOB 走溢出页不占主行(只占 9-12 字节指针),因此大字段必须用 TEXT 才能建宽表;其二,索引——VARCHAR 可建前缀索引(INDEX (col(20)))或全列索引,TEXT 必须指定前缀长度(TEXT 列不能全列索引,除非 8.0 的函数索引配合前缀),且 VARCHAR 索引键长度按字节计受 767/3072 字节限制(utf8mb4 下 VARCHAR(255) 内安全);其三,默认值——MySQL 8.0.13 之前 TEXT 不能有默认值(VARCHAR 可以),8.0.13+ 表达式默认值放宽;其四,比较与存储——TEXT 不能直接 = 比较(需 CAST)在旧版有坑,8.0 已放宽。工程结论:PG 用 TEXT 无负担;MySQL 短文本用 VARCHAR(n)(默认值、索引友好),长文本用 TEXT,需显式管理前缀索引与行大小预算。

答题先讲 PG 的完全等价(varlena 存储、TOAST、索引一致,仅约束语义差异),再列 MySQL 的四大差异(行大小 65535、TEXT 必须前缀索引、默认值限制、比较细节),最后给两库各自的选型结论。

-- PostgreSQL:TEXT + 约束等价 VARCHAR(n)
CREATE TABLE t (name TEXT CHECK (char_length(name) <= 50));
-- MySQL:TEXT 必须前缀索引
CREATE TABLE t (body TEXT, INDEX idx_body (body(100)));
-- MySQL:VARCHAR 普通索引
CREATE TABLE t (name VARCHAR(50), INDEX idx_name (name));
#
★★★

5. SMALLINT/INT/BIGINT 的选择依据是什么?存储对齐与填充(alignment padding)如何影响复合行与数组的实际占用?

SMALLINT/INT/BIGINT 的选择依据是什么?存储对齐与填充(alignment padding)如何影响复合行与数组的实际占用?

  • 整数类型的范围与存储
  • 对齐填充导致的空洞
  • 字段顺序对行大小的实际影响

选择依据:按业务取值范围与未来增长选最小够用的类型——SMALLINT 2 字节(±32767)、INT/INTEGER 4 字节(±21 亿)、BIGINT 8 字节(±9.2 万亿),主键/外键常用 BIGINT(预留增长)、状态码/枚举序号 SMALLINT 足够、普通计数 INT;类型过小有溢出风险(自增主键 INT 到 21 亿后溢出)、过大浪费存储与缓存(每行多 4 字节在千万级表就是几十 MB)。Oracle 无原生 SMALLINT 语义差异、MySQL 有 TINYINT 1 字节与 UNSIGNED 扩展、PG 有 SMALLINT/INT/BIGINT 与 SMALLSERIAL/SERIAL/BIGSERIAL。

对齐填充:多数数据库按类型自然对齐访问(4 字节对齐 4 字节、8 字节对齐 8 字节),行内字段顺序不当会插入 padding 空洞——例如 PG 中 (INT, BIGINT) 顺序(4+4 填充+8=16 字节)与 (BIGINT, INT)(8+4+4=16 字节)最终相同,但 (INT, SMALLINT, SMALLINT) 紧凑(4+2+2=8)优于 (SMALLINT, INT)(2+2填充+4=8,仍有空洞)。数组与复合类型的对齐按元素类型逐个计算。影响:列顺序敏感场景(极端宽表)可手动把 8 字节字段排前减少填充;绝大多数情况差异在 1-2 字节/行,远小于索引与 TOAST 的影响,不必过度优化,但"类型选择影响整行宽度"这一点对千万级大表有意义(行宽每减 1 字节,页内行数增加、缓存命中率提升)。

答题先给三类整数的范围/字节数与选型准则(够用+预留),再讲对齐填充机制(自然对齐、行内空洞)与字段顺序的影响及数组/复合类型的延伸,最后量化"行宽对缓存的影响"并给出"不必过度优化"的平衡结论。

#
★★★

6. DATE/TIMESTAMP/TIMESTAMPTZ 的选择矩阵是怎样的?为什么跨时区业务应存 TIMESTAMPTZ 而“日历日期”用 DATE?

DATE/TIMESTAMP/TIMESTAMPTZ 的选择矩阵是怎样的?为什么跨时区业务应存 TIMESTAMPTZ 而"日历日期"用 DATE?

  • 三种时间类型的语义
  • 时区处理与存储方式
  • 场景化选型矩阵

类型语义:DATE 只存日历日期(年-月-日,无时间无时区);TIMESTAMP(timestamp without time zone)存"墙上时钟时间"(无时区信息,解释随会话时区变化);TIMESTAMPTZ(timestamp with time zone)存"瞬时时刻"(内部统一存 UTC 微秒,展示时按会话时区转换)。选型矩阵:日历日期(生日、出账日、节假日)用 DATE——不涉及时区,避免时间成分的干扰与索引冗余;业务事件时刻(下单时间、支付时间)用 TIMESTAMPTZ——需要表达"真实发生的瞬时",跨时区(多地域用户、分布式系统)下比较与换算正确;仅"记录墙上时间且所有端都在同一时区"的遗留系统可用 TIMESTAMP,但跨时区迁移必踩坑。

为什么跨时区业务存 TIMESTAMPTZ:其一,存储语义确定——TIMESTAMPTZ 存 UTC 绝对时刻,换会话时区只改显示不改数据;TIMESTAMP 存的是"无时区的文本时间",同一时刻在不同时区会话中显示相同数字,但无法换算(系统不知道它属于哪个时区);其二,比较正确——"订单 08:00 与会议 09:00 谁先"在 TIMESTAMPTZ 下按真实时刻比较,TIMESTAMP 可能因时区错位比较错误;其三,夏令时——TIMESTAMPTZ 自动处理 DST 切换,TIMESTAMP 不会。为什么"日历日期"用 DATE:生日/账期不关心时刻与时区,TIMESTAMPTZ 会引入 UTC 偏移换算导致日期漂移(如东八区 00:30 存 UTC 变前一天);DATE 语义纯粹、索引更小。

答题先给三种类型的语义定义(DATE 无时间、TIMESTAMP 墙上时间、TIMESTAMPTZ UTC 瞬时),再列选型矩阵(日期用 DATE、事件时刻用 TIMESTAMPTZ、遗留单时区可用 TIMESTAMP),最后从存储语义、比较正确性、夏令时三点论证 TIMESTAMPTZ 的必要性与 DATE 的纯粹性。

#
★★★

7. 数据类型选择如何影响缓冲缓存效率与 I/O 放大(宽表挤占缓存页、TOAST 外存、行迁移)?建模时应如何权衡?

数据类型选择如何影响缓冲缓存效率与 I/O 放大(宽表挤占缓存页、TOAST 外存、行迁移)?建模时应如何权衡?

  • 行宽与页内行数的关系
  • TOAST 外存与访问代价
  • 行迁移与页分裂

影响机制:行宽决定页内行数——8KB 页(PG)或 16KB 页(InnoDB)容纳的行数 = 页大小 ÷ 行宽,行越宽页内行数越少,同样缓存(shared_buffers/InnoDB buffer pool)能缓存的行越少,全表扫描与索引回表的页读取次数越多(I/O 放大);宽表(几十上百列、含大文本)容易"挤占缓存页",热数据命中率下降。TOAST 外存:PG 的 TOAST 把超 2KB 的变长字段(TEXT/BYTEA/JSONB)移到单独 TOAST 表(压缩+分片),主表只留指针——行宽受控但"访问大字段"要额外读 TOAST 页(随机 IO),只 SELECT 其他列时不读 TOAST(按列取)是优势;MySQL 的 TEXT/BLOB 溢出页同理(off-page 存储,行内留 20 字节指针),且 InnoDB 的 DYNAMIC 行格式按需读溢出页。

行迁移:MySQL InnoDB 中行更新导致行变长超页时整行迁移到新页(地址变化、二级索引指向旧位置再跳转),PG 堆表更新生成新版本可能跨页;高频更新大字段的表行迁移加剧写放大与碎片。权衡建议:其一,冷热列分离——大字段、长文本拆到附属表(1:1 延展表),主表保持瘦身;其二,类型最小化(能 SMALLINT 不 INT、能 VARCHAR(50) 不 TEXT);其三,按访问模式组织列宽(宽表适合"整行读"的批处理,窄表适合"热列高频"的 OLTP);其四,压缩与归档——不常访问的大字段压缩存储或迁冷存储;其五,用 EXPLAIN/统计(pg_relation_size、Information_schema 行平均宽)评估实际行宽。

答题沿"行宽→页内行数→缓存命中→I/O 放大"主线讲机制,再分别展开 TOAST/溢出页的按列访问特性与行迁移的写放大,最后给冷热列分离、类型最小化等建模权衡清单。

#
★★★

8. CHAR(n) 定长类型在现代数据库中是否仍有价值(如定长哈希值、状态码)?它与 VARCHAR 在尾随空格处理与比较语义上有何不同?

CHAR(n) 定长类型在现代数据库中是否仍有价值(如定长哈希值、状态码)?它与 VARCHAR 在尾随空格处理与比较语义上有何不同?

  • CHAR 的定长填充与存储
  • 尾随空格的比较语义(SQL 标准 vs 实际)
  • 现代库中 CHAR 的适用场景

CHAR(n) 语义:固定长度,存储时不足部分用空格右填充,检索时多数实现会去除尾随空格;VARCHAR(n) 只存实际内容。现代数据库(PG、MySQL InnoDB、SQL Server)中 CHAR 与 VARCHAR 的存储差异已被压缩技术抹平(PG 的 CHAR 仍占定长(含填充),MySQL 的 CHAR 定长且 8.0 下无 padding 优化、InnoDB 紧凑行格式下 CHAR 仍按定长存但可压缩),因此"省空间"不再是选 CHAR 的理由。尾随空格比较:SQL 标准要求 CHAR 比较忽略尾随空格('a' = 'a '),PG 遵循(char 类型比较忽略尾部空格),MySQL 的 CHAR 比较默认忽略尾随空格(排序规则驱动,PAD SPACE),VARCHAR 在 MySQL 也忽略尾随空格(比较时),PG 的 VARCHAR/text 比较不忽略('a' <> 'a ' 为真);SQL Server 的 CHAR 忽略尾随空格、VARCHAR 不忽略。这个差异是"字符串相等判断"跨库踩坑点。

CHAR 的现代价值:其一,语义约束——固定格式数据(定长哈希 hex、MD5/SHA 摘要 32/64 字符、国家代码、状态码)用 CHAR 表达"长度恒定"意图,防误存变长内容;其二,与遗留协议/外部系统对接(固定宽度字段文件);其三,少数场景利用"定长行"的固定偏移扫描。其余场景(绝大多数业务文本)用 VARCHAR 或 TEXT(PG 官方推荐 TEXT)。索引:CHAR/VARCHAR 索引长度按字节计,定长内容在索引键上没有本质差异。

答题先讲 CHAR 的定长填充语义与存储差异(现代库中空间优势消失),再重点展开尾随空格的比较语义(标准忽略、PG VARCHAR 不忽略、MySQL 两类型都忽略的差异),最后给 CHAR 的现代适用场景(定长哈希、状态码、协议对接)与默认选 VARCHAR/TEXT 的建议。

#
★★★

9. NUMERIC(p,s) 的精度与标度如何选择?超出精度插入时各数据库是报错还是舍入?聚合 SUM 的精度如何扩展?

NUMERIC(p,s) 的精度与标度如何选择?超出精度插入时各数据库是报错还是舍入?聚合 SUM 的精度如何扩展?

  • p(精度)与 s(标度)的定义
  • 超精度插入的报错/舍入行为
  • SUM 聚合的精度提升规则

NUMERIC(p,s):p 是总有效位数(精度),s 是小数位数(标度),整数位 = p-s;如 NUMERIC(10,2) 表示最大 99999999.99(8 位整数 + 2 位小数)。选型:金额按业务上限留余量(如 NUMERIC(18,2) 或 18,4)、比例/税率用高标度(DECIMAL(10,6))、统计指标按计算需要(AVG 结果标度提升)。超精度行为:PostgreSQL 报错——超出精度(整数位超 p-s)报 "numeric field overflow",小数位超出 s 时舍入(四舍五入到 s 位,如 1.005 存 NUMERIC(10,2) 得 1.01);MySQL 严格模式(默认)下整数位超限报错(ERROR 1264 out of range),小数位超 s 舍入(或按 ROUND 规则截断舍入),非严格模式(sql_mode 关 STRICT)下整数位超限截断为最大值并警告;SQL Server 超精度报错(算术溢出),小数位舍入;Oracle 超整数位报错(ORA-01438),小数舍入。规则:整数位超限一律报错或截断(不允许丢失整数),小数位超 s 大多舍入(MySQL 非严格模式警告)。

聚合 SUM 的精度扩展:标准与各库实现"SUM 的标度保持 s、精度扩展"——PostgreSQL SUM(numeric) 返回 numeric 不限精度(不溢出);MySQL SUM(DECIMAL) 返回精度提升类型(DECIMAL 结果的精度为 p+10 之类的安全扩展,MySQL 文档规定 SUM 返回的精度为最大精度);SQL Server SUM(decimal) 精度提升到 p+10(上限 38),超 38 报错;Oracle SUM 精度提升。工程含义:单值不溢出不代表 SUM 不溢出(SQL Server 38 位上限场景要预估总量),跨库大数聚合前确认返回类型与上限。

答题先定义 p/s 与选型准则,再按库列超精度行为(整数位报错/截断、小数位舍入的差异与严格模式),最后讲 SUM 的精度扩展规则(PG 不限、MySQL/SQL Server 提升、38 位上限),形成完整的精度管理认知。

#
★★★

10. ORM/驱动与数据库的类型映射有哪些常见坑(Java int 映射 int4 溢出、LocalDateTime 映射 timestamp 还是 timestamptz、Boolean 映射)?

ORM/驱动与数据库的类型映射有哪些常见坑?如 Java int 映射 int4 溢出、LocalDateTime 映射 timestamp 还是 timestamptz、Boolean 映射等?

  • 整数/长整型的映射范围
  • 时间类型的时区映射
  • 布尔的方言映射

常见坑清单:其一,整数映射——Java int(32 位)映射 PostgreSQL int4(同 32 位)安全,但若实体字段用 int 而表列是 bigint/int8 或在 MySQL 中 BIGINT→Integer 映射(MyBatis 常见)会在数值超 21 亿时溢出报错或静默截断;长 ID(雪花 ID 64 位)必须用 Java long 且注意 JavaScript 侧 Number 精度丢失;ORM 的自动类型推断(Hibernate 的 int→int4)在 MySQL 的 BIGINT UNSIGNED 上同样溢出;其二,时间映射——Java LocalDateTime 映射 PostgreSQL timestamp(无时区)语义匹配(都是墙上时间),Instant/OffsetDateTime 应映射 timestamptz;Hibernate 6 对 LocalDateTime→timestamp、Instant→timestamptz 有明确约定,乱配(LocalDateTime→timestamptz)会在跨时区会话下产生偏移错误;MySQL 的 DATETIME 对应 LocalDateTime、TIMESTAMP 范围小(2038 问题)且有时区转换;其三,Boolean——Java boolean 映射 PG boolean 原生、MySQL 常映射 TINYINT(1)/BIT(JDBC getBoolean 兼容 0/1)、SQL Server bit;驱动返回类型差异(PG 返回 Boolean、MySQL 可能返回 Byte/Integer,框架需转换);其四,数值精度——BigDecimal 映射 NUMERIC 安全,double 映射 float8 有精度损失(金额场景禁用);其五,字符串——Java String 映射 VARCHAR 安全,但 MySQL 的 VARCHAR 长度按字符而 Java 按 UTF-16 code unit,emoji 计数不一致;其六,无符号与枚举——MySQL UNSIGNED 在 JDBC 下可能映射异常。

规避策略:DDL 与实体显式声明类型(@Column(columnDefinition)、@JdbcTypeCode),避免依赖隐式推断;时间统一约定"业务时刻存 timestamptz + Instant";ID 用 long/string 承载;集成测试覆盖边界值(Integer.MAX_VALUE、2038 年、emoji 长度)。

答题按"整数溢出、时间时区、布尔、数值精度、字符串计数"五类展开常见坑(int↔int4 溢出、LocalDateTime↔timestamp/timestamptz 约定、Boolean 各库映射、BigDecimal vs double、emoji 字符计数),最后给显式类型声明与边界值测试的规避策略。

#
★★★

11. 整数溢出在 PostgreSQL(报错)与 MySQL(严格模式报错、非严格模式行为)中的差异是什么?无符号类型能否解决?

整数溢出在 PostgreSQL 与 MySQL 中的差异是什么?MySQL 严格/非严格模式行为如何?无符号类型能否解决溢出问题?

  • PG 溢出错语义
  • MySQL 严格/非严格模式的差异
  • UNSIGNED 的利弊

行为差异:PostgreSQL 整数运算溢出一律报错(ERROR: integer out of range),插入超出范围的整数值也报错,不会静默截断——语义严格安全;MySQL 取决于 sql_mode:严格模式(默认含 STRICT_TRANS_TABLES)下插入超范围整数报错(ERROR 1264 Out of range value),非严格模式(关闭 STRICT)下插入被"截断钳制"到最大值/最小值并产生 WARNING(如给 TINYINT 存 200 变成 127);运算溢出在 MySQL 中按 BIGINT 或 DECIMAL 提升后仍超限时返回错误或 NULL(取决于表达式,部分情况返回 NULL + warning)。MySQL 8.0 起默认 sql_mode 含严格模式,行为接近 PG,但历史库与显式关闭严格的实例仍会静默钳制。

无符号(UNSIGNED)能否解决:UNSIGNED 只是把取值范围平移/扩大(如 TINYINT UNSIGNED 0-255、INT UNSIGNED 0-42 亿),"防止负数"与"扩大上限"——对正数溢出(如 INT UNSIGNED 存 50 亿)依旧溢出,没有解决溢出问题,只是移动了边界;且 UNSIGNED 带来新坑:与有符号列比较/运算时类型转换陷阱(UNSIGNED - 1 等)、驱动与 ORM 的映射问题(JDBC 读 UNSIGNED BIGINT 超 long 范围)。结论:解决溢出的正道是选对类型宽度(BIGINT)并预留下限,而非 UNSIGNED;MySQL 上同时开启严格模式保证错误可见(不静默)。

答题先对比 PG(一律报错)与 MySQL(严格模式报错、非严格模式钳制截断+警告)的行为差异及 sql_mode 的影响,再分析 UNSIGNED 只是移动边界、仍会溢出并引入转换陷阱,最后给"选对宽度+严格模式"的正解。

#
★★★

12. BYTEA/BLOB 二进制类型与 Base64 文本存储在空间、索引与查询函数上的差异?

BYTEA/BLOB 二进制类型与 Base64 文本存储在空间、索引与查询函数上有何差异?

  • 二进制类型与 Base64 文本的存储
  • 空间膨胀与 TOAST
  • 查询函数与索引差异

存储差异:BYTEA(PG)/BLOB(MySQL)直接存原始字节,无编码开销;Base64 文本方案把二进制编码为 ASCII 文本(每 3 字节→4 字符 + 填充),空间膨胀约 33%,且存入 VARCHAR/TEXT 后还要按文本存储(UTF-8 下 ASCII 每字符 1 字节),合计约 1.33 倍;压缩后二进制(图片、压缩包)编码为 Base64 后压缩失效(Base64 是 ASCII 高熵,TOAST/页压缩对图片本身无效但对 Base64 也基本无效)。TOAST/溢出页:两者超阈值都走 TOAST/off-page,差异在"存储表示"——BYTEA 原字节,Base64 是文本(比较、去重、索引都按文本语义)。

索引与查询:BYTEA 支持 =、<、>(字节序比较)与 B-tree 索引、哈希索引,但业务查询("是否含某字节模式")需 LIKE 的 bytea 版本或函数(position()、substring()),全列索引意义有限(长值走前缀/哈希);Base64 文本可直接用文本 LIKE、SUBSTRING、正则,但"字节级精确匹配"要先解码(decode(col,'base64'),函数包裹不可用普通索引,需表达式索引)。函数差异:PG 提供 encode(data,'base64')/decode(text,'base64')、digest/hmac 等(pgcrypto);MySQL 提供 TO_BASE64/FROM_BASE64;JSON 中二进制无法直接存(JSONB 不支持 bytea,需 Base64 编码)。工程结论:原生二进制数据(图片、加密摘要、序列化对象)存 BYTEA/BLOB(省空间、字节精确),仅当"需要文本工具链处理(日志、JSON、导出 CSV)"或跨系统文本协议传输时才 Base64,并接受 33% 膨胀与函数解码的索引代价。

答题先讲空间(Base64 膨胀 33% 且压缩失效)、再对比索引与查询函数(bytea 字节序比较 vs 文本 LIKE、解码函数破坏索引需表达式索引)、最后按场景给结论(原生二进制用 BYTEA/BLOB,文本协议场景才 Base64)。

#
★★★

13. 为什么用字符串存 IP/MAC 是反模式?PostgreSQL 的 INET/CIDR 与 MySQL 的 INET_ATON/INET6_ATON 如何替代并支持范围查询?

为什么用字符串存 IP/MAC 是反模式?PostgreSQL 的 INET/CIDR 与 MySQL 的 INET_ATON/INET6_ATON 如何替代并支持范围查询?

  • 字符串存 IP 的缺陷(语义、排序、范围查询、空间)
  • INET/CIDR 的原生类型能力
  • MySQL 整数化方案的函数与索引

反模式原因:其一,语义缺失——字符串无法表达"网段/掩码",'192.168.1.0/24' 只是文本,不能参与子网运算(包含、重叠判断);其二,排序与比较错误——按字典序排序 IP 得到错误顺序('10.x' 排 '192.x' 前),等值判断也受格式(前导零)影响;其三,范围查询低效——"查询某网段内 IP"用字符串前缀 LIKE 或逐段解析,无法利用 B 树有序性做范围扫描;其四,空间与校验——文本多占存储,且无类型校验('999.1.1.1' 也能存),MAC 同理(格式多样、大小写、分隔符)。

替代方案:PostgreSQL 原生 INET(单 IP,可含掩码)、CIDR(网段)、MACADDR 类型——支持 < > 范围比较(按数值序)、网络运算(<< 包含、<<= 属于网段)、B-tree/GiST 索引(含掩码索引),范围查询写 WHERE ip << inet '192.168.0.0/16';MySQL 用整数化方案:IPv4 用 INET_ATON(ip)(返回 32 位整数)存 INT UNSIGNED 列,查询用 INET_NTOA 转回文本,范围查询直接整数 BETWEEN,IPv6 用 INET6_ATON(16 字节二进制)存 BINARY(16),函数索引(8.0)可加速;MySQL 8.0 无 INET 类型(MariaDB 有 INET6 类型)。工程结论:PG 用 INET/CIDR 原生类型,MySQL 用 INET_ATON/INET6_ATON 整数化 + 函数索引,两者都避免文本存储并支持高效范围/网段查询。

答题先列字符串存 IP 的四类缺陷(语义、排序、范围查询、空间校验),再分别讲 PG 的 INET/CIDR(网络运算与 GiST 索引)与 MySQL 的 INET_ATON/INET6_ATON 整数化方案,最后给按库选型结论。

-- PostgreSQL:INET 原生类型与网段查询
CREATE TABLE conn_log (client_ip INET);
SELECT * FROM conn_log WHERE client_ip << inet '192.168.0.0/16';
-- MySQL:IPv4 整数化
CREATE TABLE conn_log (client_ip INT UNSIGNED);
INSERT ... VALUES (INET_ATON('192.168.1.5'));
SELECT INET_NTOA(client_ip) FROM conn_log WHERE client_ip BETWEEN INET_ATON('192.168.0.1') AND INET_ATON('192.168.255.255');
#
★★★

14. CREATE TABLE AS 与 SELECT INTO 在 PostgreSQL 与 MySQL 中的功能差异,CTAS 是否复制约束、索引、默认值?

CREATE TABLE AS 与 SELECT INTO 在 PostgreSQL 与 MySQL 中的功能差异是什么?CTAS 是否复制约束、索引、默认值?

  • CTAS 与 SELECT INTO 的语法与兼容性
  • CTAS 复制内容的边界(数据类型、无约束)
  • 需要完整结构的替代(LIKE 合并)

语义:CREATE TABLE AS(CTAS,标准)用查询结果建新表:CREATE TABLE new AS SELECT ...,仅复制"列结构与数据",不复制约束(主键/唯一/外键/CHECK)、不复制索引、不复制默认值(PG 中 NOT NULL 与默认值都不复制,只继承列类型);SELECT INTO(FROM ... INTO 新表)在 PostgreSQL 中是 CTAS 的旧式等价写法(SQL Server 也支持 SELECT INTO),MySQL 不支持 SELECT INTO 建表(其 INTO 是变量赋值/导出文件),因此 MySQL 建表只能 CTAS。差异:PG 的 CTAS 与 SELECT INTO 等价(都可加 WITH DATA/NO DATA),MySQL 的 CTAS 同样只复制列定义(8.0 对 CHECK 有继承?不继承——CTAS 不复制约束,字符集/排序规则按列类型与库默认)。

要点:其一,CTAS 结果列的"类型"来自查询表达式推断(表达式列类型可能收窄或变宽,如 INT 聚合、VARCHAR 截断);其二,需要完整结构时用 CTAS + 手工重建约束/索引,或 CREATE TABLE LIKE(复制结构不含数据)再 INSERT,或 pg_dump --schema-only 复用;其三,物化视图/数据备份常用 CTAS WITH NO DATA 建空骨架;其四,大数据量 CTAS 是重 IO 操作,注意临时空间与锁。工程结论:CTAS 定位是"快速数据搬迁/分析快照",不承担结构复制职责;生产表结构迁移用 DDL 工具(Flyway)或 LIKE+CTAS 组合。

答题先给 CTAS 语义(复制列与数据、不复制约束/索引/默认值)与 PG 的 SELECT INTO 等价、MySQL 不支持 SELECT INTO 的差异,再讲表达式类型推断、WITH NO DATA、LIKE+CTAS 组合等要点,最后给工程定位建议。

-- CTAS:仅列结构+数据
CREATE TABLE orders_copy AS SELECT * FROM orders;
-- 空骨架(不复制数据)
CREATE TABLE orders_empty AS SELECT * FROM orders WITH NO DATA;
-- 需要完整结构:LIKE 复制结构(含约束)再加数据
CREATE TABLE orders_copy (LIKE orders INCLUDING ALL);
INSERT INTO orders_copy SELECT * FROM orders;
#
★★★

15. CREATE TABLE LIKE 与 CREATE TABLE AS 在复制表结构时的差异,LIKE 复制列定义与约束,CTAS 复制数据但不复制索引与约束。

CREATE TABLE LIKE 与 CREATE TABLE AS 在复制表结构时的差异是什么?LIKE 复制列定义与约束、CTAS 复制数据但不复制索引与约束的具体行为如何?

  • LIKE 的结构复制范围(INCLUDING 选项)
  • CTAS 的数据复制与结构缺失
  • 组合使用的场景

CREATE TABLE new (LIKE old) 复制"列定义"(类型、NOT NULL、默认值、CHECK、生成列)且通过 INCLUDING 选项扩展:INCLUDING ALL 复制主键/唯一/外键/索引/存储参数/注释等(PostgreSQL 支持 INCLUDING DEFAULTS/INDEXES/CONSTRAINTS/COMMENTS/STORAGE 等细分选项,8.0 后默认仅列定义与 NOT NULL);不复制数据(空表)。CTAS(CREATE TABLE new AS SELECT * FROM old)复制"数据与列类型"但不复制约束/索引/默认值/身份属性(自增序列不复制)。MySQL 的 LIKE 语法类似(CREATE TABLE new LIKE old 复制完整列定义与索引(含主键),但外键 8.0 可选 INCLUDE 复制、CHECK 跟随列定义复制);MySQL CTAS 不复制索引/约束。

差异本质:LIKE 是"结构克隆(含约束索引,视选项)",CTAS 是"数据快照(只带列类型)"。使用场景:需要"同结构空表"(测试表、分区建表、归档表)用 LIKE INCLUDING ALL;需要"同数据快速拷贝"(备份、分析快照)用 CTAS;两者组合(LIKE 建结构 + INSERT SELECT 灌数据)得到"完整结构与数据"的表;注意 LIKE 不会复制表的存储参数与分区定义(PG 中 PARTITION BY 需显式声明)。工程规范:迁移工具生成 DDL 更可控,LIKE/CTAS 用于临时性复制。

答题先分别定义 LIKE(列定义+可选约束索引,INCLUDING 选项)与 CTAS(数据+列类型,无约束索引)的复制范围,再对比 MySQL 的 LIKE 差异(复制索引、外键可选),最后给三种使用组合与边界(存储参数/分区不复制)。

-- PostgreSQL:完整结构克隆
CREATE TABLE t2 (LIKE t1 INCLUDING ALL);
-- 组合:结构+数据
CREATE TABLE t3 (LIKE t1 INCLUDING ALL);
INSERT INTO t3 SELECT * FROM t1;
-- MySQL:LIKE 复制列与索引
CREATE TABLE t2 LIKE t1;
#
★★★

16. CREATE TABLE 的完整语法选项(列定义、约束、表选项、存储参数)在 PostgreSQL、MySQL、SQL Server 中的方言差异是什么?

CREATE TABLE 的完整语法选项(列定义、约束、表选项、存储参数)在 PostgreSQL、MySQL、SQL Server 中的方言差异是什么?

  • 列定义与约束的标准语法
  • 表选项与存储参数的方言差异
  • 引擎/分区/字符集等选项

共性(标准子集):列定义(类型 [CONSTRAINT] [NOT NULL] [DEFAULT])、列级/表级约束(PRIMARY KEY、UNIQUE、FOREIGN KEY、CHECK)三库语法兼容。方言差异:PostgreSQL 独有——INHERITS(继承)、PARTITION BY(声明式分区)、USING 方法(表存取方法 heap)、WITH (fillfactor=...) 存储参数、TABLESPACE、GENERATED ALWAYS AS IDENTITY、UNLOGGED/TEMP 前缀、ON COMMIT 子句(临时表)、GENERATED ALWAYS AS (expr) STORED 生成列、CREATE TABLE ... (LIKE ... INCLUDING ALL);MySQL 独有——ENGINE=InnoDB(引擎选择)、DEFAULT CHARSET/COLLATE(字符集排序规则,表级)、AUTO_INCREMENT 起始值、COMMENT=''(表注释)、ROW_FORMAT(DYNAMIC/COMPACT)、PARTITION BY 旧式分区语法(8.0 起限制)、TABLESPACE 少用;SQL Server——ON [PRIMARY](文件组)、TEXTIMAGE_ON、IDENTITY(1,1)、WITH (DATA_COMPRESSION=...) 等索引/表选项、FILETABLE/FILESTREAM、CLUSTERED/NONCLUSTERED(主键/唯一)。

差异要点:其一,自增三库三种(SERIAL/IDENTITY、AUTO_INCREMENT、IDENTITY(1,1));其二,注释/字符集/引擎是 MySQL 建表标配而 PG/SQL Server 无 ENGINE/CHARSET(PG 用 schema 级 locale、库级编码);其三,分区语法 PG 声明式(PARTITION BY RANGE/LIST/HASH + 子表)、MySQL 8.0 内联分区定义(8.0 对 RANGE/LIST 子分区有限制)、SQL Server 分区函数/方案(partition scheme)分离;其四,生成列/身份列三库语法差异(PG IDENTITY、MySQL AUTO_INCREMENT+VIRTUAL/STORED 生成列、SQL Server IDENTITY/GENERATED ALWAYS);其五,临时表 ON COMMIT 仅 PG;其六,表级检查约束 MySQL 8.0.16 后才生效。迁移工具按这些选项生成目标库 DDL。

答题先给三库共有的标准子集,再按"PG 独有、MySQL 独有、SQL Server 独有"三组列方言选项(继承/分区/存储参数 vs 引擎/字符集/行格式 vs 文件组/IDENTITY/压缩),最后总结自增、分区、生成列、注释四类高频差异。

#
★★★

17. IF NOT EXISTS / IF EXISTS 在 CREATE TABLE / DROP TABLE 中的幂等性保证与潜在陷阱?

IF NOT EXISTS / IF EXISTS 在 CREATE TABLE / DROP TABLE 中的幂等性保证与潜在陷阱是什么?

  • 幂等 DDL 的语法与语义
  • 存在判断的竞态(并发 DDL)
  • 静默跳过的陷阱(结构不一致)

语义:CREATE TABLE IF NOT EXISTS t (...) 在表已存在时"静默跳过"(不报错、不校验结构),DROP TABLE IF EXISTS t 在表不存在时"静默跳过",两者共同构成幂等 DDL:重复执行不报错,适合迁移脚本、初始化脚本反复运行。PG、MySQL、SQL Server(CREATE 支持、DROP 的 IF EXISTS 三库都支持)语法兼容(SQL Server 为 DROP TABLE IF EXISTS,SQL Server 2016+)。

陷阱:其一,结构不一致——IF NOT EXISTS 只判断"同名存在"就跳过,若已有表结构与本次定义不同(缺列、类型不同、缺索引),脚本静默通过而实际结构错误,不满足预期;幂等 ≠ 校验一致,需配合迁移工具(Flyway 的 checksum、Liquibase 的 changeSet 校验)或手工核对;其二,并发竞态——两个会话同时 CREATE TABLE IF NOT EXISTS 同一表,检查与创建之间无锁保护时一方仍可能报"already exists"(MySQL 的 IF NOT EXISTS 在并发下可能报错或产生重复对象风险,PG 依赖 catalog 锁基本安全但仍有极端窗口),幂等不能替代分布式锁;其三,隐藏错误——DROP TABLE IF EXISTS 会掩盖"本应存在却不存在"的错误(如删错库的脚本),运维脚本应区分"期望存在"与"可选清理";其四,权限与大小写——IF EXISTS 判断按名称(PG 折叠小写、MySQL 受 lower_case_table_names),名称不匹配时静默跳过掩盖问题;其五,CREATE ... IF NOT EXISTS 跳过时不返回信息,审计需主动记录。工程规范:迁移工具管 DDL 版本(不用裸 IF NOT EXISTS 代替版本管理),一次性清理脚本可用 IF EXISTS,核心结构变更需显式校验。

答题先给幂等语义(存在即跳过)与三库语法兼容,再列五类陷阱(结构不一致静默通过、并发竞态、掩盖错误、名称语义、无审计),最后给"迁移工具管版本、IF NOT EXISTS 仅辅助"的工程规范。

CREATE TABLE IF NOT EXISTS t (id INT PRIMARY KEY);
DROP TABLE IF EXISTS t;
-- 结构不一致陷阱:已有 t 缺列时上面的 CREATE 静默跳过
#
★★★

18. Schema(模式)的语义与作用,PostgreSQL 的 CREATE SCHEMA 与 MySQL 的 Database 在命名空间上的等价与差异?

Schema(模式)的语义与作用是什么?PostgreSQL 的 CREATE SCHEMA 与 MySQL 的 Database 在命名空间上的等价与差异?

  • Schema 的对象分组与权限边界
  • PG 的 CREATE SCHEMA 与 search_path
  • MySQL Database=Schema 的简化

Schema 的语义:数据库内"对象的逻辑分组与权限边界",同库不同 Schema 可含同名表互不冲突,权限按 Schema 授予(USAGE/CREATE),对象引用按"当前 Schema + search_path"解析。PostgreSQL:CREATE SCHEMA app 创建模式,表创建于其中(CREATE TABLE app.users),未限定名按 search_path 顺序解析(默认 "$user", public);Schema 是权限与应用隔离的主要单元(每应用一个 Schema),可 ALTER SCHEMA OWNER、GRANT USAGE ON SCHEMA。MySQL:无独立 Schema 层,CREATE DATABASE 与 CREATE SCHEMA 是同一操作的同义语法,Database 即命名空间(USE db 切换、db.table 引用);权限按 Database 授予;同一实例可跨库引用(db2.t 显式限定)但默认当前库解析。

等价与差异:等价点——两者都是"命名空间+权限边界+对象分组"(PG 的 Schema 与 MySQL 的 Database 角色对等);差异点——PG 的 Database 之上还有实例(多个 Database 物理隔离、不能跨库 JOIN),MySQL 的 Database 之下没有 Schema 层(对象直接属于库);PG 的 search_path 可动态切换默认 Schema,MySQL 的 USE 切换默认库但跨库引用必须全限定;PG Schema 数量/粒度灵活(几十个 Schema 共享一个库的缓存与备份),MySQL 每"库"是命名空间但跨库引用普遍(同实例内物理不隔离)。多租户设计:PG 每租户一个 Schema(共享实例、隔离对象),MySQL 每租户一个 Database(或共用库加 tenant_id 过滤)。

答题先定义 Schema 的命名空间/权限/解析作用,再分别讲 PG 的 CREATE SCHEMA+search_path 与 MySQL 的 Database=Schema 同义语法,最后从层级(实例→DB→Schema vs 库→表)、切换机制(search_path vs USE)、隔离粒度三点对比并给多租户设计。

#
★★★

19. 临时表(TEMP TABLE)与永久表的差异,会话级、事务级、跨连接的可见性规则如何在三种方言中实现?

临时表(TEMP TABLE)与永久表的差异是什么?会话级、事务级、跨连接的可见性规则在三种方言中如何实现?

  • 临时表的作用域与生命周期
  • 各库可见性与清理规则
  • 与永久表的差异(索引、统计、复制)

差异:临时表(CREATE TEMP TABLE)只对创建它的会话可见(其他会话看不到、也不能引用同名临时表),生命周期随会话(或事务,视 ON COMMIT),会话结束自动删除;数据存临时存储(PG 的临时 schema 与临时文件,MySQL 的临时表可能走内存/磁盘临时引擎),不参与主数据备份与复制(PG 逻辑复制不含临时表、MySQL 主从上的临时表需注意 DROP TEMPORARY TABLE 的复制);索引可以建(两库都支持临时表索引),统计信息 PG 会收集、MySQL 临时表优化器统计有限;DDL 上临时表不进入持久 catalog 的可恢复部分(PG 存于 pg_temp schema)。

三库可见性实现:PostgreSQL——CREATE TEMP TABLE 建于 pg_temp schema(会话级),同名永久表被隐藏(解析优先 pg_temp),ON COMMIT {PRESERVE ROWS/DELETE ROWS/DROP} 控制事务结束行为;MySQL——CREATE TEMPORARY TABLE 会话级可见,连接断开自动删除(服务器重启消失),支持 ON COMMIT?MySQL 无 ON COMMIT 选项(MySQL 临时表无事务级选项,InnoDB 临时表按事务回滚但表本身会话级);SQL Server——#local 临时表(会话级,# 前缀)与 ##global 临时表(跨会话共享,全局可见直到无引用者),临时表存 tempdb,自动清理;SQL Server 无事务级临时表选项(TRUNCATE 可用)。跨连接可见性:PG/MySQL 的临时表本会话独占;SQL Server 的 ##global 例外(显式全局)。工程建议:临时表用于"会话内中间结果、复杂查询分解",避免在连接池中因会话复用产生数据残留(注意 ON COMMIT DELETE ROWS 或显式清理);大结果集优先考虑 CTE/物化视图而非临时表。

答题先讲临时表与永久表的差异(会话可见性、生命周期、临时存储、不参与复制),再分别讲三库的实现(PG 的 pg_temp+ON COMMIT、MySQL 的 TEMPORARY 会话级、SQL Server 的 #/## 与 tempdb),最后给工程建议(连接池残留清理、场景选择)。

-- PostgreSQL:事务级清空
CREATE TEMP TABLE tmp_stats ON COMMIT DELETE ROWS AS SELECT ...;
-- MySQL:会话级临时表
CREATE TEMPORARY TABLE tmp_stats (id INT PRIMARY KEY);
-- SQL Server:本地/全局临时表
CREATE TABLE #tmp (id INT);      -- 会话级
CREATE TABLE ##g_tmp (id INT);   -- 全局可见
#
★★★

20. 分区表(Partitioned Table)的声明语法(PARTITION BY RANGE/LIST/HASH)在 PostgreSQL 10+、MySQL 8.0、Oracle 上的差异是什么?

分区表(Partitioned Table)的声明语法(PARTITION BY RANGE/LIST/HASH)在 PostgreSQL 10+、MySQL 8.0、Oracle 上的差异是什么?

  • 三库的分区声明语法
  • 分区裁剪与约束排除
  • 分区维护操作的差异

声明语法差异:PostgreSQL 10+ 声明式分区——父表 CREATE TABLE t (...) PARTITION BY RANGE (created_at),子表 CREATE TABLE t_2024 PARTITION OF t FOR VALUES FROM ('2024-01-01') TO ('2025-01-01')(子表是普通表、可独立索引/约束,支持 RANGE/LIST/HASH,支持默认分区、子分区(11+)、分区键上的唯一约束需含分区键);MySQL 8.0——内联语法 CREATE TABLE t (...) PARTITION BY RANGE (YEAR(created_at)) (PARTITION p2024 VALUES LESS THAN (2025), ...)(RANGE/LIST/HASH/KEY,不支持外键引用分区表、唯一键必须含分区键、8.0 对 RANGE COLUMNS 支持多列);Oracle——PARTITION BY RANGE (col) (PARTITION p1 VALUES LESS THAN (100), ...) 与列表/哈希分区,支持间隔分区(INTERVAL)、子分区、外键引用分区表、全局/本地索引。

差异要点:其一,子分区与默认分区支持度(PG 11+ 子分区与 DEFAULT、Oracle 全支持、MySQL 无子分区(8.0 前)且无默认分区);其二,唯一约束/主键限制——MySQL 要求唯一键包含分区键、PG 10 要求分区键参与唯一约束(PG 11+ 放宽为约束需包含分区键)、Oracle 允许全局唯一索引(非分区键);其三,索引——PG 子表各自建索引(父表索引自动传播到新分区)、MySQL 分区表索引在分区内(无全局索引)、Oracle 全局/本地分区索引可选;其四,维护操作——PG 用 ATTACH/DETACH PARTITION(可并发)、MySQL ALTER TABLE ADD/DROP PARTITION(8.0 有锁限制)、Oracle ADD/SPLIT/MERGE/DROP PARTITION + EXCHANGE;其五,分区裁剪:三库都支持按分区键谓词裁剪(PG 的 constraint exclusion/partition pruning、MySQL 的 partition pruning、Oracle partition pruning),但函数包裹分区键(如 MySQL YEAR(col))会限制裁剪。

答题先分别给三库的声明语法(PG 的 PARTITION OF 子表、MySQL 内联 VALUES LESS THAN、Oracle VALUES LESS THAN+间隔分区),再对比四个差异维度(子分区/默认分区、唯一约束与分区键、索引形态、维护操作),最后提分区裁剪与函数包裹陷阱。

-- PostgreSQL 10+
CREATE TABLE t (id INT, created_at DATE) PARTITION BY RANGE (created_at);
CREATE TABLE t_2024 PARTITION OF t FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
-- MySQL 8.0
CREATE TABLE t (id INT, created_at DATE)
PARTITION BY RANGE (YEAR(created_at)) (
  PARTITION p2024 VALUES LESS THAN (2025)
);
-- Oracle
CREATE TABLE t (id INT, created_at DATE)
PARTITION BY RANGE (created_at) (
  PARTITION p2024 VALUES LESS THAN (DATE '2025-01-01')
);
#
★★★

21. 列存表(Column-Oriented)与行存表的声明差异(ClickHouse MergeTree、PostgreSQL 列存扩展、SQL Server 内存列存)的取舍场景是什么?

列存表(Column-Oriented)与行存表的声明差异(ClickHouse MergeTree、PostgreSQL 列存扩展、SQL Server 内存列存)的取舍场景是什么?

  • 行存与列存的存储布局差异
  • 三种列存实现的声明方式
  • OLAP/OLTP 的取舍场景

布局差异:行存(堆表/聚簇索引)按行连续存储,整行一次 IO 可取,适合"点查+整行更新"的 OLTP;列存按列连续存储(每列独立文件/区段),扫描只读需要的列(I/O 大幅减少)、同列同类型高压缩比(编码、字典、位图),适合"宽表窄查询+大范围聚合"的 OLAP。声明差异:ClickHouse MergeTree 系列(含 ReplacingMergeTree、SummingMergeTree 等)是默认列存引擎(CREATE TABLE ... ENGINE = MergeTree() ORDER BY key,声明排序键与分区键);PostgreSQL 无原生列存,通过扩展(如 cstore_fdw、Hydra/ParadeDB 的 columnar 扩展、Timescale 的压缩)或外部表(FDW 接列存引擎)实现,声明类似普通表但存储后端不同;SQL Server 内存列存(Columnstore Index)在普通行存表上加列存索引:CREATE CLUSTERED COLUMNSTORE INDEX cci ON t(或非聚集列存索引),批处理模式扫描;MySQL 无原生列存(8.0 无),列存需依赖外部引擎(如分析型一体机、ClickHouse 联邦查询)。

取舍场景:OLTP 点查/频繁更新/事务 → 行存(列存更新代价高、点查需行重组);OLAP 聚合/报表/扫描大表 → 列存(压缩与只读列带来数量级加速);混合负载(HTAP)→ 行存+列存副本(SQL Server 的列存索引、PG 扩展的列存副本、TiDB 的 TiFlash 列存副本)。关键考量:列存的排序键/分区键设计(ClickHouse 的 ORDER BY 决定稀疏索引效率)、压缩率与编码选择、更新频率(列存适合批量追加与低频更新)、以及生态(分析 SQL 函数支持度)。

答题先讲行存/列存的布局差异(行连续 vs 列连续、压缩、只读列 IO),再分别讲三种列存实现的声明(ClickHouse MergeTree 引擎、PG 扩展、SQL Server 列存索引),最后按 OLTP/OLAP/HTAP 三场景给取舍结论。

-- ClickHouse:MergeTree 列存
CREATE TABLE events (ts DateTime, uid UInt64, event String)
ENGINE = MergeTree() ORDER BY (ts, uid);
-- SQL Server:列存索引
CREATE CLUSTERED COLUMNSTORE INDEX cci ON fact_sales;
-- PostgreSQL:列存扩展(cstore_fdw 示例)
CREATE FOREIGN TABLE f_events (ts timestamp, uid bigint)
SERVER cstore_server OPTIONS (compression 'pglz');
#
★★★

22. 外键引用同表(自引用)、跨 Schema、跨数据库时的语法差异与限制?

外键引用同表(自引用)、跨 Schema、跨数据库时的语法差异与限制是什么?

  • 自引用外键的声明
  • 跨 Schema 引用
  • 跨数据库引用的可行性(MySQL 可、PG 不可)

三种引用的差异:自引用——外键指向本表主键(parent_id REFERENCES t(id)),语法与普通外键相同(列级/表级都行),三库都支持;注意自引用删除的级联(环)与递归问题,以及插入顺序(先父后子或延迟约束)。跨 Schema——PostgreSQL 支持 schema_a.t 引用 schema_b.u(REFERENCES schema_b.u(id),且被引用 schema 需有权限),MySQL 的"跨库外键"(db1.t 引用 db2.u)InnoDB 支持(8.0 中可声明,跨库引用可行,但需两库同实例且都 InnoDB、字符集兼容),SQL Server 支持同实例跨库引用(database.schema.table,依赖连接上下文与权限)。跨数据库(不同实例/不同服务器):三库都不支持原生外键——外键是单实例单库内的完整性机制,跨实例引用无法原子检查,需应用层/分布式方案(2PC 与校验模式)。

限制与注意:其一,被引用列必须是有唯一约束/主键的列(三库一致);其二,字符集与排序规则——MySQL 要求两侧列字符集一致(否则报错),PG 无此限制;其三,类型兼容——两侧列类型需等价(PG 较宽松、MySQL 要求类型一致、SQL Server 要求可比较);其四,MySQL 中自引用与跨库外键都要求 InnoDB;其五,分区表限制——MySQL 分区表不能作为外键引用目标(8.0),PG 分区表作为引用目标需分区键含唯一约束(11+ 支持)。工程建议:跨库外键应视为反模式(一致性依赖数据库实例内),核心数据域尽量单库内聚,跨实例引用用应用层校验或异步对账。

答题先分别给自引用(同表声明、级联与顺序问题)、跨 Schema(PG/MySQL/SQL Server 的支持与权限)、跨数据库(同实例 MySQL/SQL Server 支持、不同实例都不支持)的语法与限制,再列通用限制(唯一目标列、字符集、类型、分区表),最后给跨库外键反模式的工程结论。

-- 自引用
CREATE TABLE category (id INT PRIMARY KEY, parent_id INT REFERENCES category(id));
-- 跨 Schema(PostgreSQL)
CREATE TABLE a.t (id INT PRIMARY KEY);
CREATE TABLE b.u (id INT PRIMARY KEY, ref_id INT REFERENCES a.t(id));
-- 跨库(MySQL 同实例)
CREATE TABLE db1.t (id INT PRIMARY KEY);
CREATE TABLE db2.u (id INT PRIMARY KEY, ref_id INT, FOREIGN KEY (ref_id) REFERENCES db1.t(id));
#
★★★

23. 表的物理属性(fillfactor、toast_tuple_target、autovacuum 相关参数)在 PostgreSQL 中的含义与调优场景是什么?

表的物理属性(fillfactor、toast_tuple_target、autovacuum 相关参数)在 PostgreSQL 中的含义与调优场景是什么?

  • fillfactor 与页填充率
  • toast_tuple_target 与 TOAST 阈值
  • autovacuum 参数与调优

三个物理属性:fillfactor(默认 100)——页内元组填充率,B-tree 索引与堆表都可用:设 70-80 时页内预留 20-30% 空间给"原地更新"(PG 的 HOT 更新在同一页插入新版本),减少页分裂与真空压力,适合高频更新表;纯追加/只读表保持 100 省空间;索引 fillfactor 预留空间给随机插入减少页分裂。toast_tuple_target(默认 128 字节阈值,即超过约 2KB 触发 TOAST)——控制变长字段何时溢出到 TOAST 表:调小让更早压缩/外存(主表瘦身、按列访问更优),调大延迟 TOAST(大字段频繁整体读取时减少解压次数),适合"大字段常读"与"大字段罕读"两种相反场景的取舍。

autovacuum 参数(表级可覆盖):autovacuum_enabled(关表级禁用以配合手动 VACUUM)、autovacuum_vacuum_scale_factor/vacuum_threshold(触发阈值:行数的百分比+绝对数)、autovacuum_vacuum_cost_limit(I/O 成本限制,控制后台清理对 OLTP 的干扰)、autovacuum_freeze_max_age 相关(防事务回卷)。调优场景:高频更新的大表——降低 fillfactor(70-80)+ 提高 autovacuum 频率(降 scale_factor);大字段表——按读模式调 toast_tuple_target;批量导入后再统一 VACUUM(关 autovacuum_enabled 期间避免反复触发)。注意:fillfactor 只影响"新写入的页",存量页需 VACUUM FULL/REINDEX 才重建;参数修改用 ALTER TABLE ... SET (fillfactor = 80),可查询 pg_class.reloptions 与 pg_settings 确认。

答题分三块:fillfactor(预留空间减少页分裂与 HOT 效率、索引与堆表)、toast_tuple_target(TOAST 阈值与读写模式取舍)、autovacuum 参数族(阈值、成本限制、回卷防护与覆盖语法),最后给高频更新、大字段、批量导入三类调优场景与注意事项。

ALTER TABLE hot_table SET (fillfactor = 80);
ALTER TABLE big_text SET (toast_tuple_target = 64);
ALTER TABLE huge_table SET (autovacuum_vacuum_scale_factor = 0.05, autovacuum_vacuum_threshold = 1000);
-- 查看表级参数
SELECT reloptions FROM pg_class WHERE relname = 'hot_table';
#
★★★

24. 表空间(Tablespace)在 PostgreSQL、Oracle、MySQL 中的实现差异,逻辑表空间如何映射到物理文件系统?

表空间(Tablespace)在 PostgreSQL、Oracle、MySQL 中的实现差异是什么?逻辑表空间如何映射到物理文件系统?

  • 表空间的逻辑概念与物理映射
  • 三库实现差异
  • 使用场景(磁盘分布、备份)

概念:表空间是"逻辑存储单元"与"物理文件系统位置"之间的映射层,允许 DBA 把不同表/索引放到不同磁盘(性能分层:SSD 热表、HDD 归档)、控制磁盘配额与备份范围。PostgreSQL:CREATE TABLESPACE ts LOCATION '/data/ssd' 创建(目录路径),建表时 TABLESPACE ts 指定;每个表空间对应一个文件系统目录,表/索引在目录下存为 1GB 段文件(relfilenode),pg_default/pg_global 是系统默认表空间;迁移用 ALTER TABLE ... SET TABLESPACE。Oracle:CREATE TABLESPACE 是核心存储对象——包含数据文件(datafile),段(表/索引)分配在表空间的区(extent)中,逻辑三层(表空间→段→区→块),支持自动扩展(AUTOEXTEND)、UNDO/TEMP 专用表空间、统一区大小;映射比 PG 更精细(块级管理)。MySQL:表空间概念不同——InnoDB 有系统表空间(ibdata1)、每表文件表空间(innodb_file_per_table=ON 时每表一个 .ibd)、通用表空间(CREATE TABLESPACE 8.0)、临时表空间;MySQL 的"表空间"默认粒度是"一个表一个文件"而非跨表共享目录,映射最简单(表→.ibd 文件,含段/区/页组织);5.7 的共享表空间与独立表空间的迁移用 ALTER TABLE ... TABLESPACE。

差异总结:Oracle 表空间是"主动管理的存储容器"(文件组+区分配+备份单元),PG 表空间是"目录别名"(结构简单、段文件直接落盘),MySQL 表空间默认是"单表文件"(8.0 通用表空间可选)。使用场景:按存储介质分层(热/冷数据分盘)、大表拆分磁盘 IO、备份恢复粒度控制;注意 MySQL 每表文件模式下"表空间"概念对运维基本透明。

答题先定义表空间的逻辑→物理映射层作用(磁盘分布、配额、备份粒度),再分别讲三库实现(PG 目录别名+1GB 段文件、Oracle 数据文件+区块管理、MySQL 每表 .ibd 与通用表空间),最后给存储分层等使用场景。

-- PostgreSQL
CREATE TABLESPACE ssd_ts LOCATION '/mnt/ssd/pgdata';
CREATE TABLE hot_data (...) TABLESPACE ssd_ts;
-- Oracle
CREATE TABLESPACE app_ts DATAFILE '/u01/app/oracle/data/app01.dbf' SIZE 10G AUTOEXTEND ON;
-- MySQL 8.0 通用表空间
CREATE TABLESPACE ts_app ADD DATAFILE 'ts_app.ibd';
CREATE TABLE t (...) TABLESPACE ts_app;
#
★★★

25. INHERITS(PostgreSQL 继承表)的语义、限制与现代替代方案(分区表)是什么?

PostgreSQL 的 INHERITS(继承表)语义与限制是什么?为什么现代实现建议用分区表替代继承?

  • 继承的语义(列继承、行包含)
  • 继承的限制(唯一约束、外键、引用)
  • 声明式分区对继承的替代

INHERITS 语义:CREATE TABLE child (...) INHERITS (parent) 使子表拥有父表全部列并新增自己的列;查询父表时自动包含子表行(默认继承扫描,parent-only 用 ONLY),UPDATE/DELETE 父表按行来源分别处理;多级继承与多父继承(PG 支持多父)可组合。设计意图是"表级类型层次/水平分片",但限制多:其一,唯一约束与主键不能跨继承表(父表唯一不约束子表、子表也不能继承父表主键),行标识不可靠;其二,外键不能引用继承父表(只能引用具体表);其三,CHECK 约束可由子表继承但 NOT NULL 语义有坑(子表可加回 NULL);其四,ONLY/继承扫描的语义易错(COUNT(*) 父表含子表行、INSERT 只入指定表);其五,更新父表行不会"移动"到子表,索引与统计管理复杂。

现代替代:PostgreSQL 10+ 的声明式分区(PARTITION BY + PARTITION OF)本质取代了继承的大多数场景:分区表提供统一的父表约束传播(分区继承 CHECK 自动生成)、支持分区键上的唯一约束(11+ 需含分区键)、DML 路由(插入自动进对应分区)、ATTACH/DETACH 维护,且查询计划器对分区有专门优化(分区裁剪、并行);继承表仍适用于少数"类型层次建模"场景,但官方文档亦建议新项目优先声明式分区。工程结论:水平分片/按月归档用声明式分区,纯"表层次"建模极少用继承(可用视图或列加 type 字段替代)。

答题先讲 INHERITS 的语义(列继承、父表扫描含子行、多父)与四类限制(唯一/外键/CHECK/NOT NULL、ONLY 语义),再论证声明式分区的替代优势(约束传播、DML 路由、分区裁剪、维护操作),最后给选型结论。

CREATE TABLE measurement (id INT, city TEXT, logdate DATE);
CREATE TABLE measurement_2024 (PRIMARY KEY (id, logdate)) INHERITS (measurement);
-- 声明式分区替代
CREATE TABLE measurement_p (id INT, city TEXT, logdate DATE)
  PARTITION BY RANGE (logdate);
CREATE TABLE measurement_p_2024 PARTITION OF measurement_p
  FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');
#
★★★

26. UNLOGGED TABLE(PostgreSQL)的使用场景与崩溃恢复行为是什么?

PostgreSQL 的 UNLOGGED TABLE 使用场景与崩溃恢复行为是什么?

  • UNLOGGED 表的 WAL 行为
  • 崩溃后的数据丢失语义
  • 适用场景与注意点

UNLOGGED TABLE 语义:不写 WAL(预写日志)的表——普通表的数据变更写 WAL 用于崩溃恢复与复制,UNLOGGED 表跳过 WAL 写入,换取更快的写入速度(减少日志 IO)与更小的 WAL 量;代价是崩溃安全性与复制能力丧失:数据库崩溃(非正常关闭)或服务器宕机后,UNLOGGED 表被清空(数据不可恢复,表结构保留),且 UNLOGGED 表不参与流复制(物理从库上为空或不同步)。注意:UNLOGGED 表仍受事务控制(回滚有效、MVCC 正常),只是"崩溃后无法重放日志"。

使用场景:其一,临时性/可重建的中间数据(ETL 暂存、分析缓存、会话汇总);其二,性能敏感且可容忍崩溃丢失的缓存表(推荐/排名等可重算数据);其三,测试环境高频写入;其四,批量导入的落地表(崩溃后重导即可)。不适合:重要业务数据、需要复制到从库的数据、需要崩溃不丢失的数据。工程注意:UNLOGGED 表可转换为 LOGGED(ALTER TABLE ... SET LOGGED)但需全表写 WAL(重写);大表 UNLOGGED 期间崩溃即全空,运维要有重建流程;从库查询 UNLOGGED 表会读到空/旧数据,应用需知晓;pg_dump 会标记 UNLOGGED 并在恢复时重建(恢复时也重放不了内容)。性能收益量级:高写入场景 WAL IO 可占相当比例,UNLOGGED 能显著降延迟,但需与数据安全权衡。

答题先讲机制(不写 WAL、崩溃清空、不参与复制)与事务语义的边界(回滚正常),再列适用场景(可重建中间数据、缓存、ETL)与不适用场景(重要数据、复制需求),最后给转换语法与运维提醒。

CREATE UNLOGGED TABLE etl_stage (id INT, payload JSONB);
-- 转换回普通表(需全表写 WAL)
ALTER TABLE etl_stage SET LOGGED;
#
★★★

27. 为什么 PostgreSQL 默认会创建一个 schema 名为 public?删除 public schema 的后果与最佳实践是什么?

为什么 PostgreSQL 默认会创建一个名为 public 的 schema?删除 public schema 的后果与最佳实践是什么?

  • public schema 的历史与默认行为
  • 默认权限(PUBLIC 可创建对象)
  • 删除/加固 public 的实践

原因与历史:public schema 由 initdb 默认创建,用于"开箱即用"——新库无需建 schema 即可 CREATE TABLE(search_path 默认 "$user", public),兼容旧版本(< 15)与习惯"表直接建在库下"的用户;它的存在让默认 search_path 总有落点。安全现状:PostgreSQL 15 之前,public schema 对所有用户开放 CREATE 权限(PUBLIC 角色的 CREATE 权限默认开启),任何能连接的用户都可在 public 中建对象——这是 search_path 攻击(恶意用户建同名的函数/视图劫持查询)的温床;15 起默认收紧了 public 的 CREATE 权限(仅 owner 可建)。

删除/加固 public 的后果:DROP SCHEMA public CASCADE 会删掉其中所有对象,且默认 search_path 落点消失(未限定对象名解析报错"no schema has been selected"),需同步调整 search_path(SET search_path TO app)与应用;实践中更常用"加固"而非删除:REVOKE CREATE ON SCHEMA public FROM PUBLIC(15 前),GRANT 给指定角色;或直接删除 public 后所有对象显式入业务 schema。最佳实践:其一,每应用建独立 schema(CREATE SCHEMA app AUTHORIZATION app_owner),search_path 设为 "$user", app;其二,回收 public 的 CREATE 权限(多用户环境必须);其三,固定函数/视图的 search_path(SET search_path 于函数内)防注入;其四,保留 public 但只读(默认对象归 owner);其五,新库模板化(template1 预置 schema 与权限)保证一致性。注意删除 public 前审计依赖(扩展、迁移工具默认目标)。

答题先讲 public 的由来(initdb 默认、开箱即用、search_path 落点)与 15 前的权限风险(PUBLIC 可 CREATE、search_path 攻击),再分析删除后果(对象丢失、解析报错)与加固做法(REVOKE CREATE、业务 schema、固定 search_path),最后给最佳实践清单。

-- 加固:回收 public 的创建权限
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
-- 业务 schema + 专属 search_path
CREATE SCHEMA app AUTHORIZATION app_user;
SET search_path TO app;
ALTER ROLE app_user SET search_path TO app, public;
-- 删除 public(谨慎,需先调整 search_path)
DROP SCHEMA public CASCADE;
#
★★★

28. 表的列数与单行最大长度限制(PostgreSQL 1600 列、MySQL 4096 列、SQL Server 1024 列)背后的存储原理是什么?

表的列数限制(PostgreSQL 1600 列、MySQL 4096 列、SQL Server 1024 列)与单行最大长度限制背后的存储原理是什么?

  • 列数限制的元数据与页结构原因
  • 行大小限制(页大小、行头、溢出)
  • 超宽表的实际危害

列数限制来源:PostgreSQL 列数上限 1600——由元组头部的"列数位图"字段(t_infomask2 中的 attnum 编码,元组头中列数信息用 11 位表示,最大 2048,实际限制 1600 留余量)决定,与 pg_attribute 存储无关而是行内列数表示受限;MySQL InnoDB 列数上限 4096(InnoDB 内部列号编码限制)但实际更早受行大小约束;SQL Server 1024 列(页内列偏移数组的限制,行内每列需 2 字节偏移表头,1024×2=2048 超页结构)。行大小限制:PostgreSQL 单行默认最大约 1.6GB(TOAST 外存使行可巨大,页内行最大约 8KB-页头,超限行靠 TOAST 拆字段)、InnoDB 行大小默认约 8KB(16KB 页的约一半,DYNAMIC/COMPRESSED 下大字段溢出页存储,非溢出字段合计受 65535 字节行限制)、SQL Server 行 8060 字节(页 8KB,行内固定部分限制,大字段可溢出到单独分配单元)。

原理共性:数据库以固定大小页为 IO 单位(PG 8KB、InnoDB 16KB、SQL Server 8KB),行必须能在页内定位(行头 + 列偏移数组),列数与行宽受"页结构 + 行内偏移表示"双重约束;超限方案是"行外存储"(TOAST、overflow page、大对象单独分配)——列数无法外溢(偏移数组在行内)所以列数是硬上限,而行宽可以外溢。实践危害:接近上限的表——每行页内容纳少(缓存与 IO 放大)、更新触发行迁移、pg_attribute 膨胀、ORM 全列映射效率低;宽表设计应先做"冷热列拆分/垂直分片"而不是堆到上限。设计建议:OLTP 表控制在几十列内,超百列审视模型(多为日志/宽表分析场景)。

答题先分别解释三库列数上限的机制(PG 元组头列数位图、InnoDB 列号编码、SQL Server 页内偏移数组),再讲行宽限制与"页结构+偏移"的共性原理及行外存储的边界(列数不可外溢、行宽可),最后给宽表危害与垂直拆分建议。

#
★★

29. ALTER TABLE ... RENAME TO 与 ALTER TABLE ... RENAME COLUMN 的语法差异?

ALTER TABLE ... RENAME TO 与 ALTER TABLE ... RENAME COLUMN 的语法差异是什么?

  • 表重命名与列重命名的语法
  • 依赖对象的联动(约束、索引、视图)
  • 三库语法差异

语法:PostgreSQL——ALTER TABLE t RENAME TO new_name(表重命名);ALTER TABLE t RENAME COLUMN old_col TO new_col(也可 RENAME old_col TO new_col 省略 COLUMN 关键字);MySQL——RENAME TABLE t TO new_name 或 ALTER TABLE t RENAME TO new_name(8.0 两者均可),列用 ALTER TABLE t RENAME COLUMN old TO new(8.0+,旧版 CHANGE old new 类型);SQL Server——EXEC sp_rename 't', 'new' 与 EXEC sp_rename 't.col', 'new', 'COLUMN'(sp_rename 过程而非 ALTER 语句,SQL Server 无 ALTER TABLE RENAME 语法)。差异点:其一,PG 的 RENAME 是 ALTER 子句(支持多语句组合),MySQL 的 RENAME TABLE 是独立语句(可一次改名多张表、可跨库),SQL Server 靠存储过程;其二,对象类型——PG 的 ALTER TABLE RENAME 只改表,列需 RENAME COLUMN;MySQL 的 RENAME TABLE 也能改视图名;其三,级联影响——PG 重命名表时约束/索引名不自动跟随(可 RENAME CONSTRAINT/INDEX),引用该表的视图/外键定义自动更新(依赖重写),MySQL 重命名表时外键引用自动更新(InnoDB 更新引用)、视图依赖需注意;SQL Server sp_rename 会同步更新依赖(部分对象需手工)。

注意事项:重命名表会失效引用它的物化视图/存储过程(需重建);生产环境重命名需在低峰执行并同步应用代码与权限(GRANT 按对象名);MySQL 的 RENAME TABLE 有原子性(8.0 元数据锁内完成);PG 中改名不锁数据(仅 catalog 更新,快);SQL Server sp_rename 可能影响脚本与依赖对象(有警告)。

答题先给三库各自的表/列重命名语法(ALTER RENAME、RENAME TABLE、sp_rename),再对比对象类型范围与依赖联动(视图/外键自动更新、约束名不跟随),最后给运维注意点(低峰、权限、物化视图重建)。

-- PostgreSQL
ALTER TABLE users RENAME TO members;
ALTER TABLE members RENAME COLUMN name TO full_name;
-- MySQL
RENAME TABLE users TO members;
ALTER TABLE members RENAME COLUMN name TO full_name;  -- 8.0+
-- SQL Server
EXEC sp_rename 'users', 'members';
EXEC sp_rename 'members.name', 'full_name', 'COLUMN';
#
★★

30. PostgreSQL 中查询当前数据库所有表的命令是什么?

PostgreSQL 中查询当前数据库所有表的命令是什么?各方式有何差异?

  • psql 的 \dt 命令
  • information_schema.tables 查询
  • pg_catalog 查询

三种方式:其一,psql 交互命令 \dt 列出当前连接数据库(当前 schema,search_path 内)的所有表(加 \dt . 列出全部 schema、\dt+ 含大小与描述);其二,SQL 查询 information_schema.tables:SELECT table_schema, table_name FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND table_schema = 'public'(标准视图,含 schema 过滤,只显示有权限的对象);其三,pg_catalog 查询:SELECT n.nspname AS schema, c.relname AS table FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind = 'r'(relkind 'r' 普通表、'p' 分区父表、'v' 视图、'm' 物化视图,过滤 pg_catalog/information_schema 系统 schema)。

差异:\dt 最快捷但依赖 psql 客户端;information_schema 可移植(跨库脚本通用)但性能慢(视图层层展开)且不含某些细节;pg_catalog 是权威来源(含 relkind、大小(pg_relation_size)、行数估算(reltuples)、owner、表空间等),适合写运维脚本;注意"当前数据库"——三方式都只列当前连接的数据库(PG 数据库隔离,跨库需换连接或 dblink)。实践:日常 \dt 即可,脚本用 pg_catalog 带过滤,审计用 information_schema。

答题给三种方式(\dt、information_schema.tables、pg_catalog 的 relkind 过滤)与各自差异(交互/可移植/权威细节),最后强调"当前数据库隔离"与场景选择。

\dt          -- psql:当前 schema 所有表
\dt *.*      -- 全部 schema
SELECT table_schema, table_name FROM information_schema.tables
WHERE table_type = 'BASE TABLE' AND table_schema NOT IN ('pg_catalog','information_schema');
SELECT n.nspname, c.relname FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'r' AND n.nspname NOT IN ('pg_catalog','information_schema');
#
★★

31. UNLOGGED 表与 HEAP 表的差异(PostgreSQL 特有)?

PostgreSQL 中 UNLOGGED 表与普通 HEAP 表的差异是什么?各自适用场景如何?

  • WAL 参与差异
  • 崩溃恢复与复制差异
  • 性能与适用场景

差异核心是 WAL(预写日志):HEAP 表(普通表)每次变更写 WAL(用于崩溃恢复与流复制),UNLOGGED 表不写 WAL——因此 UNLOGGED 写入更快(省日志 IO 与 fsync 压力)、WAL 体积更小、备份更快;代价是:崩溃/宕机后 UNLOGGED 表被清空(表结构在、数据丢),不参与流复制(物理从库上无数据或不同步),且不能作为复制/备份的可靠来源。两者都使用堆存储与 MVCC(回滚、隔离级别行为相同),索引行为相同(但索引也 UNLOGGED),VACUUM/ANALYZE 机制相同。

适用场景:HEAP 表——一切需要持久与可靠的数据(默认);UNLOGGED 表——可重建的中间数据(ETL 暂存、聚合缓存、会话状态、排序辅助)、性能敏感且可容忍崩溃丢失的数据(如推荐列表缓存)、测试数据。取舍建议:先评估"崩溃丢失能否接受+能否快速重建",能则 UNLOGGED 换性能;不能则 HEAP;混合场景(部分表高频、部分关键)可同库混用;UNLOGGED 表的数据在 pg_dump 中标记、恢复到新库后为空,运维要有重建入口。另外 UNLOGGED 与 LOGGED 可转换(SET LOGGED/UNLOGGED,有重写代价)。

答题先以 WAL 为轴对比两类表的差异(写日志、崩溃恢复、复制、性能),澄清"MVCC/索引行为相同"的边界,再列各自适用场景与"能否重建"的取舍标准,最后提转换语法与运维要点。

CREATE TABLE heap_t (id INT PRIMARY KEY);           -- 普通表(默认)
CREATE UNLOGGED TABLE ul_t (id INT PRIMARY KEY);    -- 无日志表
ALTER TABLE ul_t SET LOGGED;                        -- 转普通表
#
★★

32. 如何在 MySQL 中查看表的 DDL?SHOW CREATE TABLE t 的输出包含哪些信息?

如何在 MySQL 中查看表的 DDL?SHOW CREATE TABLE t 的输出包含哪些信息?

  • SHOW CREATE TABLE 的用法
  • 输出内容(列、约束、引擎、字符集、分区)
  • 与 information_schema 的对比

查看 DDL:SHOW CREATE TABLE t 返回两列(Table、Create Table),Create Table 列是"可重新执行的完整建表语句"(MySQL 规范化后的 DDL)。包含信息:列定义(类型、NOT NULL、默认值、AUTO_INCREMENT、生成列)、键与约束(PRIMARY KEY、UNIQUE、KEY 索引、FOREIGN KEY、CHECK(8.0.16+))、表选项(ENGINE=InnoDB、DEFAULT CHARSET/collation、AUTO_INCREMENT 当前值、COMMENT 表注释、ROW_FORMAT)、分区定义(8.0 中 PARTITION BY ... 内联输出)、8.0 的 SECURITY/列注释等。注意输出是"MySQL 方言规范化"的(如反引号、ENGINE 显式写出、隐式索引展开),可直接用于建表克隆、迁移导出与 DDL 审计。

差异与替代:SHOW CREATE VIEW、SHOW CREATE PROCEDURE 同族;information_schema.tables 可查引擎/字符集/行数(估算)等属性字段,information_schema.columns 查列级细节,但"完整 DDL"只有 SHOW CREATE TABLE 提供;mysqldump --no-data 生成带索引与约束的 DDL(比 SHOW CREATE 更完整,含表空间/统计信息可选);8.0 的 SHOW CREATE TABLE 会包含生成列表达式与 CHECK 约束(旧版不含)。注意:输出会过滤权限(无权限的表返回 NULL);AUTO_INCREMENT 显示的是当前值(可被误认为 DDL 固定值);迁移到其他库需方言转换(反引号、ENGINE)。

答题先讲 SHOW CREATE TABLE 的用法与"可重放 DDL"的性质,再枚举输出包含的信息(列/约束/引擎/字符集/分区/注释),最后对比 information_schema(属性查询)与 mysqldump --no-data(更完整)并提醒方言与权限细节。

SHOW CREATE TABLE orders\G
-- information_schema 补充查询
SELECT ENGINE, TABLE_COLLATION, TABLE_ROWS, TABLE_COMMENT
FROM information_schema.tables WHERE table_schema = 'app' AND table_name = 'orders';
#
★★

33. 如何在 PostgreSQL 中查询指定 schema 下所有表?

如何在 PostgreSQL 中查询指定 schema 下的所有表?各写法差异是什么?

  • information_schema 过滤写法
  • pg_catalog 过滤写法
  • psql 的 \dt schema.*

三种写法:其一,psql:\dt schema_name.* 直接列出指定 schema 的表(\dt app.*),最快;其二,information_schema:SELECT table_name FROM information_schema.tables WHERE table_schema = 'app' AND table_type = 'BASE TABLE'(标准、可移植,但只显示当前用户有权限的表且性能一般);其三,pg_catalog:SELECT c.relname FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = 'app' AND c.relkind IN ('r','p')(relkind 'r' 普通表、'p' 分区父表;若只想要普通表用 'r';含系统表过滤不需要因为按 schema 过滤)。差异:information_schema 的 table_type 区分 BASE TABLE/VIEW,pg_catalog 用 relkind(r/p/v/m/S 等更细粒度);pg_catalog 还可带 pg_relation_size(c.oid)、reltuples(行数估算)、obj_description(注释)、表 owner(pg_get_userbyid)与表空间;information_schema 的标准表名区分大小写与转义规则(PG 折叠小写)。实践:脚本用 pg_catalog(信息全、可控),可移植代码用 information_schema,交互用 \dt。

答题给三种写法(\dt schema.*、information_schema 按 table_schema 过滤、pg_catalog 按 nspname+relkind 过滤)与各自特点(交互/可移植/权威细节),最后给场景建议。

\dt app.*
SELECT table_name FROM information_schema.tables
WHERE table_schema = 'app' AND table_type = 'BASE TABLE';
SELECT c.relname AS table_name, pg_relation_size(c.oid) AS size_bytes
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = 'app' AND c.relkind IN ('r','p');
#
★★

34. 表名 schema.table 在 PostgreSQL 中的简写规则是什么?

表名 schema.table 在 PostgreSQL 中的简写规则是什么?未限定名如何解析?

  • 限定名与未限定名的解析
  • search_path 的查找顺序
  • pg_catalog/pg_temp 的隐式优先级

规则:全限定名 schema.table 直接按指定 schema 解析(如 app.users,不再查 search_path);未限定名(users)按 search_path 中的 schema 顺序依次查找,找到第一个命中的即用;若 search_path 中所有 schema 都没有,报错 relation does not exist(不因"某个 schema 没有"而自动改找别的,是按顺序第一个命中)。search_path 默认 "$user", public:先查与当前用户同名的 schema(存在才生效),再查 public。隐式优先级:pg_temp(当前会话临时表 schema)与 pg_catalog(系统目录)总是优先于 search_path 中列出的 schema——即使未写入 search_path,pg_catalog 也会被隐式查询(等价于排在 search_path 最前),pg_temp 同理(会话级临时表优先于同名永久表)。

细节:其一,CREATE TABLE 未限定时默认建在 search_path 第一个 schema($user 不存在则为 public);其二,schema 名与表名都遵循标识符折叠规则(小写);其三,search_path 用 SET search_path TO app, public 修改,影响后续语句;其四,函数解析同样走 search_path(同名函数优先级),是 search_path 攻击的利用点;其五,写全限定名(app.users)可绕过 search_path 不确定性(安全与明确),但代码冗长。工程规范:多 schema 应用显式设置 search_path 或使用限定名。

答题先讲限定名直接解析与未限定名按 search_path 顺序查找的规则,再讲 pg_temp/pg_catalog 的隐式优先级与默认 "$user", public 行为,最后提建表落点、函数解析与限定名/显式 search_path 的工程规范。

SELECT * FROM app.users;          -- 全限定,不查 search_path
SET search_path TO app, public;   -- 之后 users 优先解析到 app.users
CREATE TABLE t (...);             -- 建在 search_path 第一个 schema(app)
SELECT * FROM pg_catalog.pg_class LIMIT 1;  -- 系统目录可直接限定
#
★★

35. 表的 owner 概念在 PostgreSQL 中的语义是什么?

表的 owner 概念在 PostgreSQL 中的语义是什么?owner 拥有哪些权限?如何变更?

  • owner 的身份与默认权限
  • owner 的权限集(DDL、所有权限)
  • ALTER TABLE OWNER 与权限迁移

owner(属主)是"创建表(或后来被赋予)的角色",表记录在 pg_class.relowner。owner 语义:拥有表的全部权限(无需 GRANT 即隐式 ALL PRIVILEGES:SELECT/INSERT/UPDATE/DELETE/TRUNCATE/REFERENCES/TRIGGER),可执行 DDL(ALTER/DROP/加索引/加约束),可授权给别人(GRANT)、可设置表级安全;对 owner 而言无"权限不足"问题。其他角色只有被显式 GRANT 的权限,且默认新建表对其他角色无任何权限(除非库/模式级默认权限 DEFAULT PRIVILEGES)。

变更与影响:ALTER TABLE t OWNER TO new_owner 转移属主(需是 owner 或超级用户,且新 owner 需有该 schema 的 CREATE 权限);变更 owner 后:旧 owner 不再自动拥有权限(其之前 GRANT 出去的权限保留)、该表的默认权限(ALTER DEFAULT PRIVILEGES 针对 owner 的设置)不再适用新 owner、依赖该表的对象(序列、索引)跟随(索引跟随表 owner)、外部权限(GRANT)不随 owner 变更而撤销。应用场景:schema 级对象管理(应用角色统一属主)、权限迁移(把脚本建的表归到服务账号)、多租户隔离。注意:删除 owner 角色会转移其对象(REASSIGN OWNED)或删除失败(有对象依赖时);owner 与"超级用户"不同(superuser 是角色属性,表 owner 是对象属性)。

答题先定义 owner 与隐式 ALL PRIVILEGES + DDL 能力,再讲与其他角色的权限差异(无显式授权即无权限),最后讲 ALTER OWNER 的变更影响(旧 owner 权限撤销、默认权限失效、索引跟随)与 REASSIGN OWNED、owner 与 superuser 的区分。

ALTER TABLE app.users OWNER TO app_owner;
-- 批量转移
REASSIGN OWNED BY old_role TO new_role;
-- 查看 owner
SELECT relname, pg_get_userbyid(relowner) AS owner FROM pg_class WHERE relname = 'users';
#
★★

36. PostgreSQL 物化视图的 REFRESH MATERIALIZED VIEW CONCURRENTLY 需要满足的索引要求是什么?

PostgreSQL 物化视图的 REFRESH MATERIALIZED VIEW CONCURRENTLY 需要满足什么索引要求?非并发刷新与并发刷新有何差异?

  • CONCURRENTLY 的前提(唯一索引)
  • 并发刷新的锁与可见性
  • 与普通 REFRESH 的差异

前提要求:REFRESH MATERIALIZED VIEW CONCURRENTLY 要求物化视图上存在"唯一索引"(唯一约束或唯一索引均可,可建在任意列上,通常建在视图的主键/唯一业务键上)——并发刷新通过"计算新结果 → 与新结果对比 → 在新结果上增量 DELETE/INSERT 变化行"实现,唯一索引用于在旧物化数据中定位需要删除/替换的行;没有唯一索引则报错(ERROR: cannot refresh materialized view ... concurrently without a unique index)。建议索引列使用视图数据中唯一稳定的列(如聚合键、id 列)。

差异与机制:普通 REFRESH(不带 CONCURRENTLY)用"全量重建"——获取排他锁(ACCESS EXCLUSIVE)期间查询被阻塞,刷新结束后瞬间完成切换(清空重写),实现简单、无需唯一索引、对脏数据容忍度高(可直接用 REPLACE 语义);并发刷新不阻塞读(持有 ACCESS EXCLUSIVE 锁时间短,刷新过程查询可继续读到旧数据快照),但要求唯一索引、刷新更慢(要逐行对比计算增量)、且不能与正在进行的并发刷新同时进行(一次只能一个并发刷新)。选择:可接受短暂阻塞且数据量大 → 普通 REFRESH(更快);需要刷新期间持续可读(7x24 报表)→ CONCURRENTLY + 唯一索引。注意:并发刷新需要额外临时空间存储增量;视图数据无唯一键时需先加唯一索引(聚合场景给分组键建唯一索引)。

答题先讲 CONCURRENTLY 的唯一索引前提与原因(增量定位),再对比普通刷新(排他锁全量重建、快)与并发刷新(不阻塞读、增量对比、慢)的机制与取舍,最后给选择建议与唯一索引的建立方法。

CREATE MATERIALIZED VIEW mv_sales AS
  SELECT order_date, SUM(amount) AS total FROM orders GROUP BY order_date;
CREATE UNIQUE INDEX mv_sales_date_idx ON mv_sales(order_date);
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_sales;
#
★★

37. WITH CHECK OPTION 子句在可更新视图中的作用,防止插入或更新到视图不可见的行。

WITH CHECK OPTION 子句在可更新视图中的作用是什么?它如何防止插入或更新视图不可见的行?

  • 可更新视图的写入规则
  • WITH CHECK OPTION 的语义
  • LOCAL 与 CASCADED 的作用范围

默认行为:可更新视图允许 INSERT/UPDATE(把写入落到基表),但标准只要求"行能写入",不检查写入后的行是否仍满足视图定义条件——于是可能插入"视图看不到的行"(如视图 WHERE status='ACTIVE',却插入 status='INACTIVE' 的行,写入了但查询视图永远看不到,产生"幽灵数据")。WITH CHECK OPTION 强制"写入后的行必须满足视图定义":INSERT/UPDATE 违反视图条件时报错回滚,保证视图内可见性一致(写进去的必然查得到)。

作用细节:WITH CHECK OPTION 只影响 INSERT 与 UPDATE(DELETE 不涉及可见性);判断基于"修改后的新行"是否满足视图 WHERE 条件(含连接条件下视图的更新限制);对基于简单查询(单表、无聚合/去重/窗口)的可更新视图生效,复杂视图(不可更新)用 INSTEAD OF 触发器实现时同样可用 CHECK OPTION 语义。LOCAL 与 CASCADED:WITH LOCAL CHECK OPTION 只检查本视图定义的条件下推后的可检查部分(且默认行为对嵌套视图仍向上传递),WITH CASCADED CHECK OPTION 检查本视图及所有底层视图的条件(递归校验整条视图链);不指定时默认 CASCADED(PostgreSQL 中 CASCADED 是默认)。应用场景:按状态/租户过滤的视图,防止写入越界数据;API 层用视图约束写入范围。注意:CHECK OPTION 与"视图可更新性"联动——不可更新的视图上声明无效(MySQL 会报错或忽略,PG 对含 INSTEAD OF 触发器视图有效)。

答题先讲默认行为的"幽灵行"问题(写入但不可见),再定义 WITH CHECK OPTION 的强制语义与作用范围(INSERT/UPDATE、新行满足视图条件),最后讲 LOCAL/CASCADED 的嵌套校验差异与应用场景。

CREATE VIEW active_users AS
  SELECT id, name, status FROM users WHERE status = 'ACTIVE'
  WITH CHECK OPTION;
INSERT INTO active_users (name, status) VALUES ('x', 'INACTIVE');
-- 报错:new row violates check option for view
#
★★

38. 可更新视图(Updatable View)的判定规则,哪些视图允许 INSERT/UPDATE/DELETE?PostgreSQL 的 INSTEAD OF 触发器如何扩展?

可更新视图(Updatable View)的判定规则是什么?哪些视图允许 INSERT/UPDATE/DELETE?PostgreSQL 的 INSTEAD OF 触发器如何扩展视图可更新性?

  • 可更新视图的条件(简单查询)
  • 不可更新的查询特征
  • INSTEAD OF 触发器的扩展

判定规则(标准与 PG/MySQL 一致):视图的查询必须是"简单可更新"的——单一基表(无 JOIN 或多表)、无 DISTINCT、无 GROUP BY/HAVING/聚合、无窗口函数、无集合运算(UNION 等)、SELECT 列表中无表达式列(需可映射回基表列)、WHERE 可含条件;且所有可更新列直接对应基表列。满足时 INSERT/UPDATE/DELETE 可下推到基表执行;否则视图不可直接更新(写入报错 "cannot insert into view")。MySQL 的判定(可更新视图要求)类似:无聚合/去重/分组、无 UNION、FROM 单表、所有列都可映射;MySQL 8.0 还要求视图无 WITH CHECK OPTION 冲突等。分区视图(UNION ALL 多表)在 Oracle/SQL Server 中可更新(PG/MySQL 不可直接更新)。

INSTEAD OF 触发器的扩展:PostgreSQL(以及 Oracle)允许在视图上定义 INSTEAD OF 触发器——把视图的 INSERT/UPDATE/DELETE 重定向到触发器函数,函数内自行决定如何修改基表(可写多表、做校验、日志),从而让"复杂视图(JOIN、聚合展示)也可写":CREATE TRIGGER ... INSTEAD OF INSERT ON v FOR EACH ROW EXECUTE FUNCTION fn()。这是"视图写能力"的通用扩展机制;PG 也支持视图上的普通 BEFORE/AFTER 行触发器(8.3+,作用于可更新视图的语句),但 INSTEAD OF 是为不可更新视图定制的标准方案。注意:INSTEAD OF 触发器中 NEW/OLD 表示视图行的逻辑值,函数负责拆解写入基表;触发器在视图上的存在使其成为"可更新视图"(即使查询复杂)。

答题先列可更新视图的判定规则(单表、无聚合/去重/分组/窗口/集合运算、列可映射),再说明不可更新视图的写入报错,最后重点讲 INSTEAD OF 触发器如何把任意视图变成可写(重定向函数拆解写入基表)并对比 MySQL 的规则。

-- 可更新视图(单表简单查询)
CREATE VIEW v_emp AS SELECT id, name, dept_id FROM emp WHERE active = true;
INSERT INTO v_emp (id, name, dept_id) VALUES (1, 'a', 10);  -- 直接写基表
-- INSTEAD OF 触发器扩展复杂视图
CREATE FUNCTION ins_v() RETURNS trigger AS $$
BEGIN
  INSERT INTO emp (id, name) VALUES (NEW.id, NEW.name);
  INSERT INTO emp_ext (id, ext) VALUES (NEW.id, NEW.ext);
  RETURN NEW;
END $$ LANGUAGE plpgsql;
CREATE TRIGGER trg_ins INSTEAD OF INSERT ON v_complex
  FOR EACH ROW EXECUTE FUNCTION ins_v();
#
★★

39. 物化视图(Materialized View)的刷新策略(ON DEMAND、ON COMMIT、FULL、FAST、FORCE)的差异与适用场景?

物化视图(Materialized View)的刷新策略(ON DEMAND、ON COMMIT、FULL、FAST、FORCE)的差异与适用场景是什么?

  • 触发方式:ON DEMAND vs ON COMMIT
  • 刷新方式:FULL/FAST/FORCE 的差异
  • 各库支持与场景

两个维度:触发时机与刷新方法。触发时机:ON DEMAND——由应用/调度显式刷新(REFRESH MATERIALIZED VIEW),数据滞后但可控、对源库无写负担,适合可容忍延迟的报表与聚合;ON COMMIT——源表事务提交时自动刷新(Oracle 原生支持、PostgreSQL 不支持 ON COMMIT 物化视图(临时表才有 ON COMMIT),SQL Server 的索引视图自动同步(等同 ON COMMIT 语义)),数据实时但与源事务耦合(提交变慢、需要物化日志)、适合实时性要求高的场景。刷新方法(Oracle 维度):FULL——全量重建(简单可靠、开销大);FAST——增量刷新(基于物化视图日志(MLOG)只刷变化数据,快但要求可快速刷新条件:基表有主键/物化日志、视图类型支持);FORCE——优先 FAST、条件不满足自动降级 FULL(默认)。PostgreSQL:只有全量刷新(REFRESH MATERIALIZED VIEW 重建,CONCURRENTLY 是"不锁读的增量式全量"——先算全量再对比增量应用,仍基于全量计算);MySQL 无物化视图;SQL Server 用索引视图(CREATE VIEW ... WITH SCHEMABINDING + 唯一聚集索引),DML 时自动增量维护。

适用场景:ON DEMAND + 全量——低频大报表、ETL 层聚合快照(简单可控);ON DEMAND + FAST——源库高频变化、需及时但非实时的增量报表(Oracle 用物化日志);ON COMMIT——实时驾驶舱、订单汇总(能接受写放大);PG 场景——低峰定时全量刷新或 CONCURRENTLY 增量式刷新保证可用性。工程结论:先定"延迟容忍度"再定"刷新方法",PG 无 FAST/ON COMMIT 需用触发器或工具链(pg_cron)近似。

答题先分触发时机(ON DEMAND 显式可控 vs ON COMMIT 提交自动)与刷新方法(FULL 全量/FAST 增量需物化日志/FORCE 自适应)两个维度,再列各库支持矩阵(Oracle 全支持、PG 仅全量+CONCURRENTLY、SQL Server 索引视图自动维护、MySQL 无),最后给场景选型。

#
★★

40. 级联视图(View on View)的查询优化,内层视图是否会被合并(View Merging)?优化器何时选择不合并?

级联视图(View on View)的查询优化:内层视图是否会被合并(View Merging)?优化器何时选择不合并?

  • 视图合并(View Merging)机制
  • 不可合并的查询特征
  • 级联视图的性能影响

视图在查询优化中默认"被展开合并"(View Merging/Subquery Flattening):优化器把视图定义内联到外层查询,与外层谓词/连接统一优化(谓词下推、连接顺序重排),级联视图(视图套视图)会逐层展开,最终等价于"直接写底层查询",性能与手写等价——因此"视图嵌套多一层"本身不带来额外代价。PostgreSQL 默认总是合并视图(除非有强制物化的因素),MySQL 8.0 的优化器也会把简单视图/派生表合并(derived_merge),Oracle 的视图合并(MERGE hint 控制)是成熟特性,SQL Server 类似(简单视图内联)。

优化器选择不合并的场景:其一,视图/子查询含聚合、DISTINCT、GROUP BY、窗口函数、LIMIT(物化边界)——展开后语义改变(聚合先于外层过滤),必须物化(先算视图结果再与外层交互);其二,含集合运算(UNION/INTERSECT)的视图(PG 早期不合并 UNION 视图、8.x 后逐步支持);其三,视图引用 volatile 函数(每行求值差异)或 OFFSET/LIMIT 语义敏感;其四,含 DISTINCT 的视图若合并会破坏去重语义(PG 会拒绝合并);其五,外层查询有副作用限制(如 SELECT FOR UPDATE 与视图组合)。不合并的影响:谓词无法下推到视图内部(扫描多、中间结果大)、视图被物化(临时表开销);MySQL 的 derived_merge 关闭或视图含 GROUP BY 时同样物化。 优化建议:检查 EXPLAIN 中是否出现 Materialize/Subquery Scan(物化迹象);复杂聚合视图把过滤条件下推到内部(或重写为 CTE 并评估);利用 PG 的 auto_explain 与视图合并失败原因定位。结论:级联视图默认等价于展开手写,性能取决于底层查询本身;只有"物化边界"(聚合/去重/LIMIT)才阻止合并并引入额外开销。

答题先讲默认的视图合并机制(内联展开、统一优化、级联视图逐层合并等价手写),再列不合并的五类特征(聚合/去重/窗口/LIMIT、集合运算、volatile、FOR UPDATE),最后给 EXPLAIN 验证与物化边界的优化建议。

#
★★

41. 视图的依赖追踪,被引用表结构变更后视图如何处理?PostgreSQL 的 VIEW 自动失效与重建机制?

视图的依赖追踪如何处理被引用表的结构变更?PostgreSQL 的视图自动失效与重建机制是什么?

  • 视图与基表的依赖关系
  • 结构变更后的失效与自动修复
  • 级联依赖与 DROP 行为

视图的依赖追踪:PostgreSQL 在 pg_depend 中记录视图对基表(及列)的依赖(自动依赖,automatic dependency),基表结构变更时系统自动处理:删除被引用列——DROP COLUMN 时若存在依赖该列的视图,默认 RESTRICT 拒绝并报错 "cannot drop column ... because other objects depend on it",需显式 CASCADE 才级联删除依赖视图;列改名/类型变更——视图定义存储为"解析后的查询树",重命名基表列或表时视图自动跟随(查询树引用的是列 OID 而非名字),查询视图自动用新名,无需重建;表重建(DROP+CREATE)——对象 OID 变化,旧视图失效报错 "relation ... does not exist"(依赖被破坏),需重建视图。

失效与重建机制:PostgreSQL 视图依赖"对象 OID + 列编号"(解析树已规范化),所以重命名等 DDL 后视图仍有效(自动重解析);结构破坏性变更(删表/删列)则依赖检查(ALTER TABLE ... DROP COLUMN 会因依赖报错或需 CASCADE 级联删除视图)。检查依赖:pg_depend 查询(refobjid 指向被引对象)、psql 的 \d+ 视图显示依赖、pg_describe_object 报错。MySQL 的差异:MySQL 视图定义存"创建时的文本+校验",基表结构变更后视图不会自动更新定义(列变更可能导致查询时报列不存在,需 CREATE OR REPLACE 重建);SQL Server 有 sp_refreshview 刷新元数据(列顺序变化后需刷新)。

答题先讲 PG 的依赖记录与自动处理(查询树引用 OID、改名自动跟随、删列/删表触发依赖检查与级联),再对比 MySQL(文本定义、需手动 CREATE OR REPLACE)与 SQL Server(sp_refreshview),最后给依赖查询与生产变更流程建议。

-- 查看视图依赖(引用表)
SELECT v.relname AS view_name, t.relname AS depends_on
FROM pg_depend d
JOIN pg_rewrite r ON r.oid = d.objid
JOIN pg_class v ON v.oid = r.ev_class
JOIN pg_class t ON t.oid = d.refobjid
WHERE d.refclassid = 'pg_class'::regclass AND v.relname = 'my_view';
-- MySQL 重建视图
CREATE OR REPLACE VIEW my_view AS SELECT ...;
#
★★

42. 视图(View)的本质是什么?它是存储查询还是存储数据?PostgreSQL 与 MySQL 的 view implementation rule(可更新视图判定)如何?

视图(View)的本质是什么?它是存储查询还是存储数据?PostgreSQL 与 MySQL 的可更新视图判定规则如何?

  • 视图是存储的查询定义(虚表)
  • 查询时展开的执行语义
  • 可更新视图的判定差异

本质:视图是"存储的查询定义"(命名查询/虚表),不存储数据——数据始终来自基表(物化视图除外);查询视图时优化器把视图定义展开(内联)进外层查询统一执行,视图本身无物理存储、无额外 IO(除非被物化)。因此视图的作用是"封装与复用查询逻辑"(简化 SQL、权限隔离(只授视图权限)、逻辑抽象(屏蔽底层结构变化)),而代价是"每次查询都重新执行视图定义"(与直接写底层查询等价)。

实现与判定:PostgreSQL 视图存储为"解析后的查询树 + 重写规则(pg_rewrite)",查询时经规则重写展开;可更新判定——视图查询为单表简单查询(无聚合/DISTINCT/分组/窗口/集合运算、SELECT 列表列可直接映射基表列、FROM 单表)时允许 INSERT/UPDATE/DELETE,否则仅可 SELECT;MySQL 判定(8.0)——同样要求无聚合/去重/分组/UNION、FROM 单表且列可映射(还有更细规则:视图不能引用系统表、不能有 LIMIT 等),并支持 WITH CHECK OPTION;MySQL 的可更新视图在 8.0 中判定较严格(如含 JOIN 的视图不可更新),PG 相同(JOIN 视图不可直接更新,可用 INSTEAD OF 触发器)。差异点:PG 的"列编号映射"实现使列改名不影响视图;MySQL 存定义文本,结构变化需重建;PG 视图上的行级触发器可进一步控制可更新视图的写入行为(MySQL 无视图触发器)。

答题先讲本质(存储查询非数据、查询时展开执行、无物理存储与 IO),再分别讲 PG(查询树+重写规则、单表简单查询可更新)与 MySQL(文本定义、类似判定规则)的实现与可更新判定,最后对比差异(OID 依赖 vs 文本、视图触发器)。

#
★★

43. 为何 OLTP 系统不推荐使用复杂视图?视图嵌套的查询优化难度如何?

为何 OLTP 系统不推荐使用复杂视图?视图嵌套的查询优化难度如何?

  • 复杂视图的执行开销
  • 嵌套视图的物化与优化限制
  • OLTP 场景的替代方案

OLTP 不推荐复杂视图的原因:其一,复杂视图(JOIN 多表、聚合、嵌套子查询)在 OLTP 高频点查下每次执行都做全量/大范围计算——视图展开后若无索引支撑(聚合视图无法走普通索引、多表 JOIN 在大数据下成本高),延迟不可控;其二,视图嵌套(视图套视图)形成"物化边界"(聚合/去重/LIMIT 阻止合并),优化器无法跨层下推谓词,中间结果膨胀,执行计划难以收敛到最优;其三,可维护性与排障——复杂视图掩盖真实查询,执行计划(EXPLAIN)难以定位瓶颈,且视图依赖链让结构变更牵一发动全身;其四,锁与并发——视图本身不锁,但复杂视图背后的大范围扫描/聚合与 OLTP 高频小查询的定位相悖。

优化难度:优化器对"简单可合并"的嵌套视图可以逐层展开全局优化(等价手写),但一旦出现不可合并特征(聚合、DISTINCT、窗口、LIMIT、集合运算),只能物化中间层——物化的临时结果无法利用基表索引,谓词下推被阻断,优化器退化为"先算完内层再过滤外层",计划质量显著下降;多层嵌套叠加物化边界时问题放大(每一层都损失优化机会)。 工程建议:OLTP 中视图只用于"简单过滤封装+权限隔离";复杂分析逻辑用——物化视图(预计算)、CTE(语句级、可控制物化与内联)、专门的报表查询或 OLAP 引擎;多层嵌套前先检查 EXPLAIN 的 Materialize 节点与谓词下推情况,必要时手动展开视图评估真实成本。

答题先列 OLTP 不推荐复杂视图的四点原因(执行开销、优化限制、可维护性、定位矛盾),再重点分析嵌套视图的物化边界与谓词下推阻断机制,最后给替代方案(物化视图/CTE/分析引擎)与 EXPLAIN 检查建议。

#
★★

44. 视图与 CTE 的语义差异,CTE 是语句级,视图是 schema 级对象。

视图与 CTE 的语义差异是什么?CTE 是语句级、视图是 schema 级对象的含义与影响是什么?

  • 作用域与生命周期差异
  • 复用性(视图跨语句复用、CTE 单语句)
  • 物化与优化差异

差异核心:作用域与生命周期——CTE(WITH ... AS)是语句级:只在当前一条 SQL 语句内可见与复用(同语句内可多次引用),语句结束即消失,不持久、不入目录、不占 schema 对象;视图是 schema 级对象:持久存储在数据库目录中(pg_views/pg_rewrite),可被任意语句、任意会话、任意应用跨语句复用,直到被 DROP。因此:视图适合"跨请求复用"(封装公共查询、权限隔离、供报表/BI 工具引用);CTE 适合"单条复杂语句内分层组织"(可读性、避免重复子查询、控制物化)。

其他差异:其一,优化——视图查询时展开(合并或按规则重写),CTE 由优化器决定内联或物化(PostgreSQL 的 MATERIALIZED/NOT MATERIALIZED 控制),CTE 多次引用可能重复执行(非物化时);其二,命名冲突与权限——视图有 owner/权限管理(GRANT SELECT ON view)、CTE 无独立权限(跟随外层语句权限);其三,递归——递归只能 CTE(WITH RECURSIVE),视图无递归定义;其四,依赖——视图建立对基表的依赖(pg_depend),CTE 无持久依赖;其五,可更新性——视图可声明可更新+CHECK OPTION,CTE 本身只是查询表达式不可直接"更新"。工程结论:跨语句公共逻辑用视图,单语句分层用 CTE;两者可组合(视图内可用 CTE、CTE 中可引用视图)。

答题先点明核心差异(CTE 语句级、视图 schema 级),再展开生命周期、复用、权限/owner、递归、依赖、可更新性六个维度的差异,最后给"公共逻辑视图、单语句 CTE"的选型结论。

#
★★

45. PostgreSQL 中刷新物化视图的命令是什么?

PostgreSQL 中刷新物化视图的命令是什么?两种刷新方式的差异是什么?

  • REFRESH MATERIALIZED VIEW 语法
  • 普通与 CONCURRENTLY 刷新
  • 权限与依赖要求

命令:REFRESH MATERIALIZED VIEW mv_name;(全量重建,需物化视图 owner 或超级用户权限)。两种方式:普通刷新——获取 ACCESS EXCLUSIVE 锁重建全部数据(期间所有对该视图的 SELECT 被阻塞,完成后立即切换到新数据),实现简单、无需前置条件、执行较快(整批重建);并发刷新——REFRESH MATERIALIZED VIEW CONCURRENTLY mv_name;:不阻塞读(刷新过程中查询继续返回旧快照),内部"计算全量新结果 → 与旧数据对比 → 增量 DELETE/INSERT 变化行",前提是视图上有唯一索引(用于定位变化行),且刷新期间不能与其他并发刷新同时进行;并发刷新较慢(对比开销+增量写入),但满足 7x24 可读需求。

细节:其一,刷新是事务性的(失败自动回滚到旧数据);其二,普通刷新在刷新瞬间视图为空(重建中)——CONCURRENTLY 无此窗口;其三,依赖——物化视图可被引用它的视图/其他物化视图依赖,刷新不影响依赖;其四,调度——PostgreSQL 无内置调度,生产用 pg_cron、外部 cron、或应用定时任务执行 REFRESH;其五,MATERIALIZED VIEW 的 SELECT 权限不授予时刷新仍可(owner 执行);其六,数据量大时普通刷新需要临时空间(重建文件),并发刷新同样需要额外空间存增量。选型:能接受短暂阻塞选普通刷新(更快),报表 7x24 选 CONCURRENTLY(先建唯一索引)。

答题先给两条命令与权限要求,再对比普通(排他锁全量重建、快、有阻塞窗口)与并发(不阻塞读、需唯一索引、增量对比、较慢)的实现机制与适用场景,最后补充事务性、调度(pg_cron)与临时空间细节。

REFRESH MATERIALIZED VIEW mv_sales;                 -- 全量重建
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_sales;    -- 不阻塞读(需唯一索引)
-- 定时刷新(pg_cron 示例)
SELECT cron.schedule('0 2 * * *', $$REFRESH MATERIALIZED VIEW mv_sales$$);
#
★★

46. SQL Server 的 indexed view 与 PostgreSQL 的 materialized view 区别?

SQL Server 的 indexed view(索引视图)与 PostgreSQL 的 materialized view(物化视图)有何区别?

  • 索引视图的创建条件(SCHEMABINDING、聚集索引)
  • 自动维护 vs 手动刷新
  • 可用性与查询改写

核心区别:维护时机与使用方式。SQL Server 索引视图(Indexed View):在普通视图上创建"唯一聚集索引"后成为物化对象(CREATE VIEW ... WITH SCHEMABINDING + CREATE UNIQUE CLUSTERED INDEX),数据由 SQL Server 在基表 DML 时自动增量维护(事务内同步更新,等同 ON COMMIT 物化)——查询总是最新,无手动刷新;但要求苛刻:视图必须 SCHEMABINDING(禁止底层结构变更)、基表须有 SET 选项约束(ANSI_NULLS、QUOTED_IDENTIFIER 等固定)、不能有非确定性函数、聚合需 COUNT_BIG 等;查询使用需"查询匹配"(查询优化器只在满足特定条件(企业版自动匹配、其他版本需 NOEXPAND 提示)时用索引视图改写)。

PostgreSQL 物化视图:CREATE MATERIALIZED VIEW 存储查询结果,但维护靠手动 REFRESH(全量或 CONCURRENTLY 增量式),数据滞后于基表(快照语义);无自动维护(无 ON COMMIT),无 SCHEMABINDING 要求(基表结构变更后刷新可能失败),查询时总是使用物化数据(无条件匹配问题)。差异总结:SQL Server 索引视图 = 实时自动同步 + 严格创建约束 + 优化器选择性使用;PG 物化视图 = 手动快照刷新 + 简单创建 + 查询总命中。适用场景:SQL Server 用于"需要实时一致且能承受写放大"的场景(且多为 Enterprise 许可特性);PG 用于"可容忍延迟的报表聚合"。其他对比:MySQL 无物化视图;Oracle 物化视图支持 ON COMMIT/FAST(最接近 SQL Server 的实时增量)。迁移注意:把 SQL Server 索引视图迁到 PG 需改为物化视图+定时刷新(接受延迟)或触发器/应用层维护。

答题先讲 SQL Server 索引视图的机制(SCHEMABINDING+唯一聚集索引、DML 自动增量维护、查询匹配/NOEXPAND),再对比 PG 物化视图(手动 REFRESH 快照、简单创建、查询总命中),最后给场景选型与迁移注意。

-- SQL Server 索引视图
CREATE VIEW v_sales WITH SCHEMABINDING AS
  SELECT order_date, SUM(amount) AS total, COUNT_BIG(*) AS cnt
  FROM dbo.orders GROUP BY order_date;
CREATE UNIQUE CLUSTERED INDEX uci ON v_sales(order_date);
-- PostgreSQL 物化视图
CREATE MATERIALIZED VIEW mv_sales AS
  SELECT order_date, SUM(amount) AS total FROM orders GROUP BY order_date;
REFRESH MATERIALIZED VIEW mv_sales;
#
★★

47. WITH LOCAL CHECK OPTION 与 WITH CASCADED CHECK OPTION 的差异?

WITH LOCAL CHECK OPTION 与 WITH CASCADED CHECK OPTION 的差异是什么?默认行为是什么?

  • 嵌套视图的检查范围
  • LOCAL 只查本视图、CASCADED 递归查链
  • 默认 CASCADED 的语义

差异在"嵌套视图链"上的检查范围:WITH LOCAL CHECK OPTION——只检查"当前视图定义的条件"(以及底层已带 CHECK OPTION 的视图条件;标准规定 LOCAL 仍会向上传递到已声明 CHECK OPTION 的下层视图);WITH CASCADED CHECK OPTION——递归检查"本视图及所有底层视图"的条件(无论底层是否声明),即写入必须同时满足整条视图链的 WHERE 条件。示例:v1(WHERE status='A')→ v2(基于 v1,WHERE amount>0):对 v2 的 INSERT,CASCADED 同时要求 status='A' 且 amount>0;LOCAL 只要求 amount>0(除非 v1 自身带 CHECK OPTION)。

默认行为:不指定时按 CASCADED 处理(PostgreSQL 文档明确 CHECK OPTION 默认 CASCADED;Oracle 默认 CASCADED)。MySQL 同样支持 LOCAL/CASCADED(默认 CASCADED)。选择建议:LOCAL 语义宽松(只约束本视图)、适合"分层视图各自约束自己的写入范围";CASCADED 严格(约束整链)、适合"底层视图是权威数据边界"(防止绕过下层视图条件写入)。注意:底层视图不可更新时 CHECK OPTION 传播范围受可更新性影响(不可更新部分不参与检查);CASCADED 链上任何一层不可更新视图会导致整链不可写(除非 INSTEAD OF 触发器)。

答题先用嵌套示例说明 LOCAL(本视图+已声明下层)与 CASCADED(递归整链)的检查范围差异,再确认默认 CASCADED 与两库一致,最后给选择建议与可更新性联动的注意点。

CREATE VIEW v1 AS SELECT * FROM users WHERE status = 'ACTIVE'
  WITH LOCAL CHECK OPTION;
CREATE VIEW v2 AS SELECT * FROM v1 WHERE amount > 0
  WITH CASCADED CHECK OPTION;
-- v2 的 INSERT 必须同时满足 status='ACTIVE' 与 amount>0
#
★★

48. 如何删除一个视图?DROP VIEW 还是 DROP MATERIALIZED VIEW?

如何删除一个视图?DROP VIEW 与 DROP MATERIALIZED VIEW 的区别是什么?

  • 两类视图的删除语句
  • CASCADE 与依赖处理
  • IF EXISTS 幂等

语法:普通视图用 DROP VIEW [IF EXISTS] view_name [CASCADE|RESTRICT];物化视图用 DROP MATERIALIZED VIEW [IF EXISTS] mv_name [CASCADE|RESTRICT]。两者不能混用(DROP VIEW 删除物化视图会报错 "is not a view",DROP MATERIALIZED VIEW 删普通视图同样报错)。区别:物化视图是"存储数据的对象",删除会直接删除其存储(物理数据)与索引;普通视图只删定义(无数据)。IF EXISTS 幂等(不存在时发出 NOTICE 不报错);RESTRICT(默认)——有依赖对象(其他视图引用它、物化视图、存储过程、外键引用视图?)时拒绝删除并报错;CASCADE——级联删除依赖它的对象(引用它的视图、物化视图、触发器)。

注意点:其一,依赖检查——被其他视图/物化视图/函数引用时默认 RESTRICT 报错,需先删依赖或 CASCADE(谨慎:CASCADE 可能连带删除不相关的引用视图);其二,权限——需视图 owner 或超级用户;其三,事务性——PG 中 DROP VIEW 是事务性 DDL(可回滚);MySQL 中 DROP VIEW 可用 IF EXISTS 但旧版不支持 CASCADE(8.0 语法仍无 CASCADE 选项,依赖由系统处理);其四,物化视图删除前无需先刷新;其五,删除视图不影响基表数据(视图只是定义)。工程规范:删除前用 pg_depend 或 \d+ 确认依赖关系,生产删除加事务包裹并在低峰执行。

答题先给两类删除语句与"不能混用"的区分(物化视图带存储),再讲 IF EXISTS/RESTRICT/CASCADE 的依赖处理语义,最后列权限、事务性、MySQL 差异与删除前的依赖确认建议。

DROP VIEW IF EXISTS v_users;
DROP VIEW v_users CASCADE;                 -- 级联删除引用它的对象
DROP MATERIALIZED VIEW IF EXISTS mv_sales;
DROP MATERIALIZED VIEW mv_sales CASCADE;
#
★★

49. 物化视图的 REFRESH FAST 选项需要哪些前置条件?

物化视图的 REFRESH FAST(增量刷新)选项需要哪些前置条件?不满足时如何处理?

  • FAST 刷新的机制(物化视图日志)
  • 前置条件清单(主键、日志、视图形态)
  • FORCE 与条件不足的降级

REFRESH FAST 的前置条件(Oracle 体系,PG 无 FAST 概念):其一,物化视图日志(Materialized View Log,MLOG$_基表)——基表必须建物化视图日志(CREATE MATERIALIZED VIEW LOG ON t WITH ROWID/PRIMARY KEY, SEQUENCE INCLUDING NEW VALUES),日志记录基表的变化(INSERT/UPDATE/DELETE 的行标识与新旧值),增量刷新据此只重算变化部分;其二,视图形态——FAST 刷新支持的视图类型受限:简单视图(单表、可含连接但需满足物化连接日志条件)、聚合视图(COUNT/SUM 等可增量聚合的函数,MIN/MAX/AVG 有额外条件)、以及包含 UNION ALL 的分区物化视图等,并非任意 SQL 都支持 FAST;其三,基表主键或 rowid 可用(日志以主键或 rowid 标识变化行);其四,物化视图已建合适的索引(如聚合视图的 GROUP BY 列上的索引)用于定位受影响行。

其他条件:物化视图日志需与基表同 schema、基表 DML 时日志同步写入(有写放大);CONTAINER MAP(分区场景)、外部表与分布式表通常不支持 FAST。处理:不满足 FAST 条件时,FORCE(默认)自动降级为 FULL 全量刷新(结果正确、开销大);显式 REFRESH FAST 在条件不足时直接报错(ORA-23413 等)。工程建议:先评估"是否值得 FAST"——FAST 引入日志写放大与维护复杂度,若刷新频率低(日级)FULL 更简单;增量需求高(分钟级)才建日志并严格按支持列表设计视图(聚合可增量的函数)。

答题先讲 FAST 的机制(物化视图日志记录变化、增量重算),再列前置条件(日志+主键/rowid、视图形态支持列表、索引),最后讲 FORCE 降级与显式 FAST 报错及"日级 FULL、分钟级 FAST"的工程取舍。

-- Oracle:建物化视图日志(FAST 的前提)
CREATE MATERIALIZED VIEW LOG ON orders WITH PRIMARY KEY, ROWID
  INCLUDING NEW VALUES;
CREATE MATERIALIZED VIEW mv_sales
  REFRESH FAST ON DEMAND AS
  SELECT order_date, COUNT(*) cnt, SUM(amount) total FROM orders GROUP BY order_date;
#
★★

50. 视图与函数(FUNCTION)在数据封装上的取舍?

视图与函数(FUNCTION)在数据封装上有何取舍?各自适用场景是什么?

  • 视图的声明式封装 vs 函数的程序式封装
  • 查询能力与优化差异
  • 权限与复用维度

本质差异:视图是"声明式的查询封装"——封装 SELECT 逻辑,可直接被 SELECT/JOIN/子查询引用,优化器可展开、下推谓词、与外部查询统一优化,结果集可继续参与关系运算(是关系);函数是"程序式的封装"——接收参数、执行过程逻辑、返回标量/表/集合,逻辑不受优化器改写(黑盒),通常按参数逐次执行。取舍维度:查询能力——视图可被优化器改写(谓词下推、合并),函数(尤其 volatile)每调用执行、结果不可继续参与优化;参数化——视图不能带参数(需 WHERE 条件或函数化),函数天然参数化(按输入返回不同结果),动态过滤场景函数更直接;复用——视图适合"固定的关系视图"(报表列集合、权限裁剪),函数适合"带参数的查询模板"(如 get_orders_by_user(uid));副作用——函数可含 DML/过程逻辑(存储过程能力),视图纯只读(可更新视图是特例);权限——两者都可 GRANT,但视图能"只暴露列子集"而函数只暴露结果契约。

工程取舍:查询被多次复用作 JOIN 源 → 视图;需要参数化、过程逻辑(循环、分支、DML)或封装多语句 → 函数;性能关键路径 → 视图(可优化、可下推)优于函数(每行调用开销、无索引内推);注意 SQL 函数可内联(PG 的 LANGUAGE SQL 简单函数优化器可展开)缩小差距,但 PL/pgSQL 过程函数不可。结论:能视图化就视图化(声明式、可优化),函数用于参数化与过程性需求;两者可组合(视图内部调用函数、函数内部查询视图)。

答题先对比本质(声明式关系封装 vs 程序式过程封装),再按查询能力(可优化/可下推 vs 黑盒)、参数化、复用形态、副作用、权限五个维度展开取舍,最后给"优先视图、函数补过程能力"的结论。

#
★★

51. PL/pgSQL(PostgreSQL)与 T-SQL(SQL Server)的语法差异,变量声明、循环、异常处理、游标使用的对比。

PL/pgSQL(PostgreSQL)与 T-SQL(SQL Server)在变量声明、循环、异常处理、游标使用上的语法差异是什么?

  • 变量声明与赋值语法
  • 循环与条件控制
  • 异常处理与游标

变量声明与赋值:PL/pgSQL——DECLARE 段声明(v_id INT; v_name TEXT;),赋值用 := 或 =(v_id := 1;),变量类型可 %TYPE/%ROWTYPE;T-SQL——DECLARE @id INT(@ 前缀),赋值 SET @id = 1 或 SELECT @id = 1(SELECT 可从查询赋值)。循环:PL/pgSQL——FOR i IN 1..10 LOOP ... END LOOP、WHILE 条件 LOOP、FOR r IN SELECT ... LOOP(行循环)、EXIT/CONTINUE;T-SQL——WHILE 条件 BEGIN ... END(唯一循环,配合 BREAK/CONTINUE 与游标)、无 FOR 数值循环(用 WHILE 或计数变量)。条件:PL/pgSQL 用 IF ... THEN ... ELSIF ... ELSE ... END IF 与 CASE;T-SQL 用 IF ... BEGIN ... END ELSE BEGIN ... END(无 ELSIF,用 ELSE IF 嵌套)。异常处理:PL/pgSQL——BEGIN ... EXCEPTION WHEN unique_violation THEN ... END(按异常名/条件捕获,块级),RAISE EXCEPTION/RAISE NOTICE(输出调试);T-SQL——BEGIN TRY ... END TRY BEGIN CATCH ... END CATCH(捕获后 ERROR_MESSAGE()/ERROR_NUMBER() 取错误信息),RAISERROR/THROW(抛错);注意 PL/pgSQL 的 EXCEPTION 块会开启子事务(回滚块内变更),T-SQL 的 TRY/CATCH 需 XACT_STATE() 判断事务状态。

游标:PL/pgSQL——DECLARE cur CURSOR FOR SELECT ...、OPEN cur、FETCH cur INTO var、CLOSE(也可 FOR r IN SELECT 简化);T-SQL——DECLARE cur CURSOR FOR、OPEN、FETCH NEXT FROM cur INTO、@@FETCH_STATUS 判断、CLOSE/DEALLOCATE。共性:都建议"能集合查询就别用游标"(游标逐行慢);差异点集中在符号(@、:=)、块语法(LOOP/END vs BEGIN/END)与异常模型(块捕获 vs TRY/CATCH)。迁移要点:过程语言不可直接移植(PL/pgSQL ↔ T-SQL 需重写),先用集合操作重构再翻译控制流。

答题按变量(@/:=/%TYPE)、循环(LOOP/WHILE vs WHILE+游标)、异常(EXCEPTION WHEN vs TRY/CATCH 与子事务差异)、游标(FETCH 与 @@FETCH_STATUS)四个维度对比,最后给"集合优先"共识与迁移重写建议。

-- PL/pgSQL
CREATE FUNCTION f() RETURNS void AS $$
DECLARE v_id INT := 1; v_name TEXT;
BEGIN
  FOR i IN 1..10 LOOP
    IF i % 2 = 0 THEN CONTINUE; END IF;
    RAISE NOTICE 'i=%', i;
  END LOOP;
EXCEPTION WHEN unique_violation THEN
  RAISE EXCEPTION 'duplicated';
END $$ LANGUAGE plpgsql;
-- T-SQL
CREATE PROCEDURE p AS
BEGIN
  DECLARE @i INT = 1;
  WHILE @i <= 10 BEGIN
    IF @i % 2 = 0 BEGIN SET @i += 1; CONTINUE; END
    RAISERROR('i=%d', 0, 1, @i);
    SET @i += 1;
  END
END;
#
★★

52. 函数体内调用函数的开销与优化,函数内联(Function Inlining)在 PostgreSQL 中的实现条件?

函数体内调用函数的开销与优化是什么?PostgreSQL 的函数内联(Function Inlining)实现条件是什么?

  • 函数调用的执行开销来源
  • PG 内联的条件(LANGUAGE SQL、IMMUTABLE/STABLE、简单体)
  • 内联的收益与失败场景

函数调用开销来源:每次调用都要经过函数调用机制(参数传递、上下文切换、权限检查、可能重新解析/执行计划),对"每行调用"的查询(WHERE func(col) > 10)开销放大;非内联时优化器无法把函数体内的谓词与外部条件联合优化(无下推、无索引利用),且 PL/pgSQL 函数每次调用有解释执行开销。优化手段:函数内联(Inlining)——优化器把函数体表达式展开进调用处(如同宏替换),消除调用开销并让函数体内表达式参与全局优化(如 func(col) 展开后 col > 条件 可用索引)。

PostgreSQL 内联条件(PG 11+ 增强):其一,LANGUAGE SQL 函数(非 PL/pgSQL;PL/pgSQL 不能内联);其二,标记 IMMUTABLE 或 STABLE(VOLATILE 不内联,避免重复执行副作用);其三,函数体为"简单表达式"(单条 SELECT、无 INSERT/UPDATE/DELETE、无动态 SQL、无变量与流程控制;8.0 起支持部分更复杂语句,但含副作用的仍不内联);其四,参数与返回值类型匹配(内联时参数替换为调用实参);其五,无安全相关限制(SECURITY DEFINER 的敏感场景谨慎)。内联收益:体积小、调用频繁的 SQL 函数(如类型转换、简单计算封装)查询速度显著提升;不满足条件时用 STABLE + 简单体尽量促成内联,复杂逻辑保留函数形态并注意"函数内无法下推"的成本(改用 CASE 表达式或视图替代)。

答题先讲函数调用的开销来源(每行调用、无联合优化、解释执行),再列 PG 内联的条件(LANGUAGE SQL、IMMUTABLE/STABLE、简单体、类型匹配)与失败场景(PL/pgSQL、VOLATILE、含 DML),最后给收益与替代建议。

CREATE FUNCTION plus_tax(amount numeric) RETURNS numeric
LANGUAGE SQL IMMUTABLE AS $$ SELECT amount * 1.13 $$;
-- 可内联:WHERE plus_tax(price) > 100 展开为 price * 1.13 > 100(可用表达式索引)
#
★★

53. 函数的安全定义者(SECURITY DEFINER)与调用者(INVOKER)权限模式的差异与权限滥用风险。

函数的安全定义者(SECURITY DEFINER)与调用者(SECURITY INVOKER)权限模式有何差异?存在哪些权限滥用风险?

  • 两种执行权限模型的差异
  • SECURITY DEFINER 的提权风险
  • 规避措施(search_path 固定、最小权限)

权限模型差异:SECURITY INVOKER(默认)——函数以"调用者"的身份与权限执行,访问的表/对象按调用者权限检查(调用者无权限则函数内访问失败),适合"函数即调用者代码的延伸";SECURITY DEFINER——函数以"定义者(owner)"的身份与权限执行,即使调用者权限不足,也能访问定义者有权访问的对象(如普通用户调用 DBA 写的函数读敏感表),常用于"受控提权"(提供受限的数据访问入口、绕过表的直接授权)。执行环境差异:定义者模式下,函数内"未限定的对象名"按定义者的 search_path 解析(除非函数内显式 SET search_path),权限提升随函数执行而生效。

权限滥用风险:其一,提权漏洞——若定义者是超级用户/高权角色,且函数体可被调用者利用(参数注入 SQL、函数内动态 SQL 拼接入参),调用者可能间接执行高权操作(经典的 search_path 攻击:调用者控制 search_path 让函数内未限定引用命中恶意对象);其二,越权读/写——设计不当的 SECURITY DEFINER 函数暴露了本不该开放的数据与写入能力;其三,权限保持——GRANT EXECUTE 被滥用(把高权函数执行权授给所有人)。规避措施:函数内固定 search_path(SET search_path = pg_catalog, app)防止对象劫持;SECURITY DEFINER 函数体只做最小操作(白名单参数、无动态 SQL);定义者用专用低权角色而非超级用户;按需 GRANT EXECUTE;审计定义者函数的调用;必要时 REVOKE 掉非必要 EXECUTE 权限。MySQL 对应概念:SQL SECURITY DEFINER/INVOKER(视图与存储程序),风险类似(定义者为高权账号时提权)。

答题先对比两种模式(调用者权限 vs 定义者权限)与典型用途(受控提权入口),再列三类滥用风险(提权漏洞、search_path 劫持、越权暴露),最后给固定 search_path、最小权限、专用低权定义者等规避清单。

CREATE FUNCTION read_sensitive() RETURNS TABLE(...)
LANGUAGE sql SECURITY DEFINER
SET search_path = pg_catalog, app AS $$
  SELECT ... FROM app.secret_table;  -- 以定义者权限访问
$$;
GRANT EXECUTE ON FUNCTION read_sensitive() TO app_user;
#
★★

54. 存储过程的参数模式(IN、OUT、INOUT、DEFAULT)与返回值的语义差异?

存储过程的参数模式(IN、OUT、INOUT、DEFAULT)与返回值的语义差异是什么?

  • 四种参数模式的语义
  • 返回值与 OUT 参数的差异
  • 默认参数与调用方式

参数模式:IN(默认)——输入参数,过程内只读(修改不影响调用方变量),调用时传值;OUT——输出参数,过程内赋值、返回给调用方(调用时不传值或传占位,返回后读取),用于"返回多个值"(如同时返回状态码与消息);INOUT——双向,输入初值、过程内可改、最终值返回调用方(如计数器累加);DEFAULT——给 IN 参数默认值,调用时可省略(如 p_limit INT DEFAULT 10),支持按名调用(参数名=>值,PG;SQL Server 无按名?SQL Server 有 @p = value 按名传参)。语义差异:IN 是"值传入",OUT 是"值传出",INOUT 是"值传入又传出";它们影响"调用方如何读写"而非过程内部逻辑。

与返回值的差异:函数/过程可用 RETURN 返回一个值(函数必有返回值;过程用 RETURN 仅表示"结束",可无值),OUT/INOUT 参数提供"多值返回"通道——RETURN 返回"过程结果/状态",OUT 参数携带"业务数据",两者可并用(如 RETURN 成功标志 + OUT 返回结果集指针或计数)。注意:PG 中存储过程(PROCEDURE)无返回值,只能靠 INOUT/OUT 或出参游标;函数(FUNCTION)可有 RETURNS 声明(标量/表)并可同时用 OUT;SQL Server 存储过程默认返回"影响行数"或显式 RETURN 整数,数据经 SELECT 结果集与 OUTPUT 参数传递;MySQL 存储过程无 RETURN 值(只有出参),函数有 RETURNS。默认值与调用:DEFAULT 参数在调用时可省略,PG 支持位置/命名混用(命名参数提升可读性)、SQL Server 用 DEFAULT 关键字或省略尾部默认参数。

答题先定义四种模式的语义(只读输入/只传输出/双向/带默认),再对比 RETURN 返回值与 OUT 参数(单值 vs 多值通道、可并用),最后列三库差异(PG PROCEDURE 无返回值、SQL Server OUTPUT、MySQL 出参)与默认参数调用方式。

-- PostgreSQL 函数:OUT 多值 + INOUT
CREATE FUNCTION calc(a IN int, b INOUT int, c OUT int) AS $$
BEGIN
  b := b + a;      -- INOUT 可读可写
  c := b * 2;      -- OUT 输出
END $$ LANGUAGE plpgsql;
SELECT * FROM calc(1, 5);  -- b=6, c=12
#
★★

55. 存储过程的执行计划缓存(Plan Caching)与参数嗅探(Parameter Sniffing)问题如何影响性能?

存储过程的执行计划缓存(Plan Caching)与参数嗅探(Parameter Sniffing)问题如何影响性能?

  • 执行计划缓存机制
  • 参数嗅探的产生与危害
  • 解决方案(OPTION RECOMPILE、参数化、提示)

计划缓存机制:SQL Server 首次执行存储过程时优化器"嗅探"当前参数值生成执行计划并缓存(plan cache),后续调用复用该计划(省编译开销);PostgreSQL 对 PL/pgSQL 内的 SQL 语句同样按"参数化模板"缓存计划(第一次按实参优化,之后以参数复用,除非语句每次重新规划(如 PL/pgSQL 的 generic plan 机制、EXECUTE 动态 SQL 重新规划));MySQL 的存储过程内语句按预处理语句缓存,也有类似机制。参数嗅探(Parameter Sniffing)问题:缓存计划是针对"首次参数值"优化的——首次参数是"高选择性值"(如查单条)生成嵌套循环计划,后续传入"低选择性值"(如查 90% 行)仍复用嵌套循环 → 性能灾难(反之亦然);计划对数据分布敏感时"一次优化、处处复用"会失灵。

解决方案:其一,OPTION (RECOMPILE)(SQL Server)——每次重新编译(计划最优但编译开销增加,适合参数分布极不均的过程);其二,OPTION (OPTIMIZE FOR (@p = 典型值))——按典型值生成稳定计划(折中);其三,参数化查询/不使用局部变量参与嗅探(局部变量赋值会阻止嗅探但可能生成次优计划——SQL Server 经典陷阱);其四,PostgreSQL 对策——PL/pgSQL 的 EXECUTE 动态 SQL 强制每次重新规划、或修改 plan_cache_mode(auto/force_custom_plan/force_generic_plan)、或对不稳定语句用 custom plan;其五,统计信息更新与查询改写(加提示/重写为参数化良好的形式)。工程要点:监控计划缓存命中与语句级性能波动(parameter_sniffing 是 SQL Server 面试高频题),对"参数敏感"的语句用 RECOMPILE/OPTIMIZE FOR 或 PG 的 custom plan 策略,避免把全部过程一刀切加 RECOMPILE。

答题先讲计划缓存机制(首参优化、复用),再定义参数嗅探问题(首参选择性决定计划、参数分布变化后复用次优计划)及其危害,最后列三库解决方案(RECOMPILE/OPTIMIZE FOR、局部变量陷阱、PG 的 plan_cache_mode/EXECUTE)与监控建议。

-- SQL Server:按参数值强制重编译 / 按典型值优化
CREATE PROCEDURE p @id INT AS
SELECT * FROM t WHERE id = @id OPTION (RECOMPILE);
SELECT * FROM t WHERE id = @id OPTION (OPTIMIZE FOR (@id = 100));
-- PostgreSQL:动态 SQL 强制重新规划
EXECUTE 'SELECT * FROM t WHERE id = $1' USING p_id;
#
★★

56. 存储过程(Stored Procedure)与函数(Function)的本质差异,过程无返回值可执行 DDL,函数必须有返回值不可执行 DDL。

存储过程(Stored Procedure)与函数(Function)的本质差异是什么?过程无返回值可执行 DDL、函数必须有返回值且不可执行 DDL 的语义如何理解?

  • 返回值要求差异
  • DDL 执行能力差异
  • 调用方式与事务差异

本质差异(SQL 标准语义):函数(FUNCTION)必须声明返回类型并返回一个值(标量、表或集合),设计为"表达式语义"——可在 SELECT 中调用(作为表达式一部分)、可作视图/计算列/索引表达式,强调"无副作用、可组合";存储过程(PROCEDURE)不需要返回值(可用 OUT/INOUT 参数与结果集传递数据),设计为"过程语义"——执行一系列操作(DML、控制流、事务、DDL),在语句中不能直接当表达式用,调用用 CALL。DDL 能力:标准规定函数内不可执行 DDL(保持"函数是查询表达式"的纯性,避免优化器展开函数时产生副作用;PG 的普通函数中执行 DDL 受限——函数不能执行事务控制且 DDL 在函数内虽可写但 PG 14 前有限制;Oracle 函数内执行 DDL 需自治事务;MySQL 存储函数内禁止 DDL 与显式事务语句);存储过程可自由执行 DDL(建表、建索引、DDL 操作)与事务控制(BEGIN/COMMIT/ROLLBACK)。

其他差异:调用——函数 SELECT func()/表达式调用、过程 CALL proc()(PG 11+ 支持 CALL,SQL Server 用 EXEC);事务——PG 的函数运行在调用事务内(不能 COMMIT/ROLLBACK)、过程(PG 11+)可含事务控制(内部子事务);返回——函数 RETURNS TABLE 返回结果集(可直接 JOIN)、过程返回"输出参数+游标/结果集";权限与用途——函数用于计算/转换/查询封装,过程用于业务批处理(ETL 作业、批量维护)。MySQL 差异:函数不能返回结果集(只能标量)、过程可返回结果集;SQL Server 函数不能有副作用(不可修改数据库状态)、过程无限制。

答题先讲标准语义差异(函数必须有返回值、无副作用、可作表达式;过程无返回值要求、可执行 DDL 与事务),再分别说明调用方式(SELECT vs CALL/EXEC)、事务边界(函数在调用事务内)与各库实现差异(MySQL 函数不能返回结果集、SQL Server 函数无副作用),最后给选型结论。

#
★★

57. 标量函数、内联表值函数(ITVF)、多语句表值函数(MSTVF)的执行计划差异与性能影响?

标量函数、内联表值函数(ITVF)、多语句表值函数(MSTVF)的执行计划差异与性能影响是什么?

  • 三类函数的定义差异(SQL Server 体系)
  • 内联展开 vs 多语句物化
  • 每行调用开销与优化建议

三类(SQL Server 术语,PG 对应 SQL 函数/集合返回函数):标量函数(Scalar UDF)——返回单值,默认逐行调用(row-by-row),且含数据访问的标量函数阻止并行与部分优化(每个调用独立执行计划/上下文切换),性能开销最大,SQL Server 2019 起支持标量函数内联(可展开);内联表值函数(ITVF)——函数体是单条 SELECT、RETURNS TABLE,优化器把函数内联展开(如同视图/参数化视图),谓词可下推、可参与连接优化与索引利用,性能接近"直接写查询";多语句表值函数(MSTVF)——函数体多语句(声明表变量、循环、INSERT 填充)、RETURNS @t TABLE,每次调用物化到表变量(插入临时结构、无统计信息、基数估算固定默认值(如 100 行)),无法下推谓词、无法并行、计划质量差。

性能影响对比:ITVF 可内联优化(谓词下推、索引、并行)≈ 最优;标量 UDF 每行调用(可内联化后改善)≈ 中;MSTVF 强制物化+固定基数估算 → 最差(大结果集时连接计划严重失真)。工程建议:优先 ITVF(单 SELECT 返回表);避免逐行标量函数(改写为 JOIN/派生表/聚合或 CROSS APPLY 内联);MSTVF 改写为 ITVF/视图/临时表;SQL Server 2019+ 用标量 UDF 内联(数据库级 scoped configuration UDF_INLINING);PG 对应:LANGUAGE SQL 函数可内联、PL/pgSQL 返回表函数物化(RETURNS TABLE 用 RETURN QUERY 有物化差异),同样遵循"能内联优先"。

答题先定义三类函数(标量单值、ITVF 单 SELECT 表、MSTVF 多语句表变量物化),再分析执行计划差异(内联展开可下推 vs 物化固定基数估算)与性能排序,最后给工程建议(ITVF 优先、避免逐行标量、2019 内联开关)。

-- ITVF:可内联
CREATE FUNCTION dbo.fn_items(@oid INT) RETURNS TABLE AS
RETURN (SELECT * FROM items WHERE order_id = @oid);
-- MSTVF:物化到表变量,基数估算失真
CREATE FUNCTION dbo.fn_items_slow(@oid INT) RETURNS @t TABLE (id INT) AS
BEGIN
  INSERT @t SELECT id FROM items WHERE order_id = @oid;
  RETURN;
END;
#
★★

58. 确定性函数(Deterministic Function)与不确定函数(Non-deterministic Function)在查询优化器中的处理差异,例如 GETDATE()、RAND()。

确定性函数(Deterministic Function)与不确定函数(Non-deterministic Function)在查询优化器中的处理差异是什么?以 GETDATE()、RAND() 为例?

  • 确定性的定义
  • 优化器的利用(常量折叠、索引、物化视图)
  • 典型不确定函数的影响

确定性定义:相同输入必然产生相同输出的函数(如 ABS、UPPER、日期运算),不确定函数相同输入也可能不同(RAND() 每次不同、GETDATE() 随时间变化、NEWID())。优化器利用确定性:其一,常量折叠——WHERE 中确定性函数作用于常量(WHERE ABS(-1)=1)可在编译期求值;确定性函数包裹的列可建表达式索引且索引可用(LOWER(col) 索引要求函数 IMMUTABLE);其二,计划复用与缓存——含不确定函数的查询不能安全复用计划结果(PG 中 VOLATILE 函数阻止某些优化、表达式索引不允许 volatile 函数);其三,物化视图/生成列——SQL Server 索引视图要求视图内函数全部确定性(GETDATE() 不允许,需转列或使用 sysdatetime 之类仍不允许——确定性要求严格),PG 的生成列(GENERATED ALWAYS AS)要求表达式 IMMUTABLE;其四,优化器无法预知不确定函数的值,谓词评估必须逐行执行(无法折叠、无法下推为常量条件)。

GETDATE() 与 RAND() 的处理:GETDATE()(SQL Server 非确定性,虽在语句内通常返回同一值)——不能用于索引视图、函数必须确定性等约束,SQL Server 中 GETDATE 被标记为非确定性;RAND()——每次调用返回不同值,SELECT 中每行求值可能不同(取决于实现),无法参与任何确定性优化,也无法用于索引/物化;PG 对应:now()/CURRENT_TIMESTAMP 是 STABLE(同语句内相同,可建索引?PG 中 STABLE 可用于表达式索引)、random() 是 VOLATILE(禁止用于索引、物化视图刷新有差异)。实践要点:给自定义函数标注正确稳定性(IMMUTABLE/STABLE/VOLATILE)——标错(把 volatile 标成 immutable)会导致索引与物化视图数据错误;需要"语句级固定时间"用 STABLE 的 now(),需要"每次新值"用 VOLATILE 的 clock_timestamp()/random()。

答题先定义确定性与优化器的四类利用(常量折叠、表达式索引、物化/索引视图、计划缓存),再以 GETDATE/RAND 与 PG 的 STABLE/VOLATILE 对比说明典型影响,最后强调自定义函数稳定性标注错误的危害。

#
★★

59. 自定义聚合函数(User-Defined Aggregate)的实现步骤(sfunc、finalfunc、initcond、combinefunc)在 PostgreSQL 中的 CREATE AGGREGATE 语法。

自定义聚合函数(User-Defined Aggregate)的实现步骤是什么?请给出 PostgreSQL 中 CREATE AGGREGATE 的语法与各部分(sfunc、finalfunc、initcond、combinefunc)的作用?

  • 状态函数 sfunc 与初始条件 initcond
  • 最终函数 finalfunc
  • 并行/合并的 combinefunc

实现原理:聚合把"多行归约为一行"的中间状态逐步推进——sfunc(状态转移函数)逐行把"当前状态 + 当前行值"推进为新状态,initcond(初始状态)是首行之前的状态起点,finalfunc(最终函数)在全部行处理完后把状态转换为输出值(可省略,直接返回状态)。示例:自定义"乘积聚合"——sfunc 做 state * value(初始 1),finalfunc 可省略(状态即结果)。CREATE AGGREGATE 语法:CREATE AGGREGATE prod(numeric) (SFUNC = mul_state, STYPE = numeric, INITCOND = '1', FINALFUNC = ..., COMBINEFUNC = ..., PARALLEL = SAFE)。

扩展部分:combinefunc(合并函数)——把两个部分状态合并(并行聚合时每个 worker 维护部分状态,Leader 用 combinefunc 合并),实现后需 PARALLEL = SAFE 才能并行聚合;SORTOP(对 MIN/MAX 类聚合声明排序操作符以利用索引)、MSFUNC/MINVFUNC(有序集合聚合的移动聚合逆函数)、FINALFUNC_MODIFY(finalfunc 是否可修改状态)、HYPOTHETICAL(假设集合)。实现步骤:先写 sfunc/finalfunc/combinefunc 的普通函数(PL/pgSQL 或 C/SQL),再 CREATE AGGREGATE 组装,最后测试边界(空集——initcond 与 finalfunc 对空集处理:COUNT 类空集返回 0、SUM 返回 NULL 需 finalfunc 或特殊处理)。注意:聚合函数要正确处理 NULL(sfunc 中忽略 NULL 行或按需处理,类似 SUM 忽略 NULL 的语义需自己实现)。应用场景:自定义统计(中位数、百分位近似、数组收集、位聚合)、业务汇总。

答题先讲聚合的"状态推进"模型(sfunc 逐行推进 + initcond 起点 + finalfunc 收尾),再给 CREATE AGGREGATE 完整语法并逐一说明每个子句(SFUNC/STYPE/INITCOND/FINALFUNC/COMBINEFUNC/PARALLEL)的作用,最后讲实现步骤与 NULL/空集边界测试。

CREATE FUNCTION mul_state(state numeric, v numeric) RETURNS numeric
LANGUAGE SQL IMMUTABLE AS $$ SELECT COALESCE(state, 1) * v $$;
CREATE FUNCTION mul_combine(a numeric, b numeric) RETURNS numeric
LANGUAGE SQL IMMUTABLE AS $$ SELECT a * b $$;
CREATE AGGREGATE prod(numeric) (
  SFUNC = mul_state, STYPE = numeric, INITCOND = '1',
  COMBINEFUNC = mul_combine, PARALLEL = SAFE
);
SELECT prod(amount) FROM orders;  -- 全量乘积
#
★★

60. 触发器函数(Trigger Function)的 NEW、OLD、TG_OP、TG_TABLE_NAME 等特殊变量的使用场景?

触发器函数(Trigger Function)中的 NEW、OLD、TG_OP、TG_TABLE_NAME 等特殊变量的使用场景是什么?

  • NEW/OLD 的语义与可用操作
  • TG_OP 与 TG_TABLE_NAME 等元信息变量
  • 触发器函数内部使用示例

特殊变量语义(PostgreSQL 触发器函数):NEW——新行(INSERT/UPDATE 中有效,类型为记录),BEFORE 触发器中可修改 NEW 的字段来改写最终写入值;OLD——旧行(UPDATE/DELETE 中有效),只读;TG_OP——当前触发操作('INSERT'/'UPDATE'/'DELETE'/'TRUNCATE'),用于一个函数挂多种操作时分支;TG_TABLE_NAME/TG_TABLE_SCHEMA——触发表名与 schema(一个触发器函数被多表共用时区分来源);TG_WHEN(BEFORE/AFTER)、TG_LEVEL(ROW/STATEMENT)、TG_EVENT、TG_ARGV(触发器声明时 WITH 传入的参数数组,同一函数不同参数挂多表)。使用场景:NEW 修改——BEFORE INSERT/UPDATE 中做数据清洗(去空格、大写化、填充默认值、审计字段);OLD 对比——UPDATE 中判断字段是否变化(NEW.status <> OLD.status)、DELETE 中把旧行写入审计表(归档触发器);TG_OP 分支——单函数处理 INSERT/UPDATE/DELETE 三种操作(通用审计触发器);TG_TABLE_NAME——多表共用一个审计函数,记录来源表名;TG_ARGV——通用日志触发器按参数区分日志级别。

注意事项:NEW/OLD 只存在于行级触发器(FOR EACH ROW);INSERT 无 OLD、DELETE 无 NEW(访问报错或 NULL,需用 TG_OP 保护);BEFORE 触发器返回 NULL 可跳过该行操作(RETURN NULL 阻止 INSERT/UPDATE/DELETE),返回 NEW 继续;AFTER 触发器不能改 NEW(改动无效);TRUNCATE 触发器无 NEW/OLD(语句级)。MySQL 对应:NEW/OLD 相同、无 TG_OP(用 INSERT/UPDATE/DELETE 分支触发器的固定动作或触发器名区分)、有类似变量但能力弱于 PG。

答题先逐个说明 NEW(新行可改)、OLD(旧行只读)、TG_OP(操作分支)、TG_TABLE_NAME(来源表)等变量的语义与可用操作/时机,再给三类典型场景(清洗、审计、通用多表触发器)示例,最后补充行级/语句级与 BEFORE 返回 NULL 等边界规则。

CREATE FUNCTION audit_trg() RETURNS trigger AS $$
BEGIN
  IF TG_OP = 'DELETE' THEN
    INSERT INTO audit_log(table_name, op, old_data)
    VALUES (TG_TABLE_NAME, TG_OP, row_to_json(OLD));
    RETURN OLD;
  ELSE
    NEW.updated_at := now();  -- BEFORE 触发器改写
    INSERT INTO audit_log(table_name, op, old_data, new_data)
    VALUES (TG_TABLE_NAME, TG_OP, row_to_json(OLD), row_to_json(NEW));
    RETURN NEW;
  END IF;
END $$ LANGUAGE plpgsql;
#
★★

61. SQL 函数(LANGUAGE SQL)与 PL/pgSQL 函数(LANGUAGE plpgsql)的性能差异与选择依据?

SQL 函数(LANGUAGE SQL)与 PL/pgSQL 函数(LANGUAGE plpgsql)的性能差异与选择依据是什么?

  • 两种语言函数的执行机制
  • 内联与解释执行的性能差异
  • 选型依据

机制差异:SQL 函数(LANGUAGE SQL)函数体是"一个或多个 SQL 语句",由优化器直接处理——简单单语句函数可被内联(Inlining)展开进调用查询(消除调用开销、允许谓词下推与索引利用),执行即执行 SQL;PL/pgSQL 函数是过程语言:解释执行(每次调用解析/执行过程代码)、支持变量/循环/异常/游标,其内部的 SQL 语句再单独优化执行,调用开销与解释开销都存在且不可内联。性能差异:简单计算/单语句查询场景,SQL 函数(内联后)远快于 PL/pgSQL(可达数量级差距,尤其被每行调用时);复杂过程逻辑(多语句、分支循环、异常处理)只能用 PL/pgSQL,性能差异让位于功能。

选择依据:其一,功能需求——需要流程控制/变量/异常/动态 SQL → PL/pgSQL;纯查询/纯表达式(SELECT 封装、类型转换、简单计算)→ LANGUAGE SQL;其二,调用频率与内联收益——被查询高频调用(每行调用)的函数尽量写成可内联的 SQL 函数(IMMUTABLE/STABLE + 单语句);其三,可读性与维护——SQL 函数简洁声明式,PL/pgSQL 适合复杂业务过程;其四,稳定标记——两种语言都需正确标注 IMMUTABLE/STABLE/VOLATILE(影响索引与缓存);其五,其他库对应——MySQL 的 SQL 函数(简单)与过程(BEGIN...END 复合)类似、SQL Server 的内联表值函数 vs 多语句函数同理。结论:默认优先 SQL 函数(简单可内联),需要过程能力时用 PL/pgSQL,并对高频调用函数做内联检查(EXPLAIN 看是否展开)。

答题先对比机制(可内联的声明式 vs 解释执行的过程式),再讲性能差异的量化场景(每行调用差距大),最后按功能需求、调用频率、可读性给选型依据与内联检查建议。

#
★★

62. 如何在 PostgreSQL 中调试 PL/pgSQL 过程?请说明 RAISE NOTICE/EXCEPTION 的用法与断点调试工具。

如何在 PostgreSQL 中调试 PL/pgSQL 过程?RAISE NOTICE/EXCEPTION 的用法与断点调试工具是什么?

  • RAISE 级别与输出通道
  • RAISE EXCEPTION 的抛错与回滚
  • pldebugger 等调试工具

调试手段分三层:其一,日志输出——RAISE NOTICE 'msg %', var(输出到客户端(psql 直接显示)与服务器日志(log_min_messages 控制),% 占位符替换变量值)、RAISE LOG(只进日志)、RAISE DEBUG(调试级别,默认不显示)、RAISE INFO/WARNING;RAISE 无级别时默认 EXCEPTION。其二,主动抛错——RAISE EXCEPTION 'msg', var 抛出错误(可带 ERRCODE 'unique_violation' 或 USING HINT/DETAIL 补充信息),调用方捕获(BEGIN...EXCEPTION WHEN)或中止事务,用于"断言式调试"(在关键分支检查状态、不满足即报错定位)与业务校验;注意 EXCEPTION 抛出会中止当前事务(除非在子事务块内捕获)。其三,断点调试——pldebugger(PostgreSQL Debugger API 扩展 + pgAdmin/pgAdmin 调试插件或 debugger 客户端):设置断点、单步执行、查看变量、进入函数;也可用第三方工具(DBeaver 对 PL/pgSQL 的调试支持有限);轻量方案:在函数关键处加临时 RAISE NOTICE 输出变量与 TG_OP 等、用 DO 块包裹测试调用、配合 auto_explain 观察函数内 SQL。

最佳实践:开发期用 RAISE NOTICE 打点(输出入参、中间状态、SQL 结果);生产函数保留 RAISE LOG/EXCEPTION 的审计与错误信息(含 ERRCODE 与 DETAIL 便于排障);复杂函数引入 pldebugger 单步调试;结合单元测试(pgTAP)覆盖分支。注意 RAISE NOTICE 在客户端无显示时检查 log_min_messages 与 client_min_messages;RAISE EXCEPTION 前把需要保留的信息写入日志(异常导致事务回滚时临时表数据丢失)。

答题按三层展开:RAISE 输出族(NOTICE/LOG/DEBUG 与 % 占位)、RAISE EXCEPTION 抛错(ERRCODE/USING、事务中止语义、断言式调试)、断点工具(pldebugger + 客户端),最后给开发/生产的最佳实践与消息级别配置提醒。

RAISE NOTICE 'processing user %', uid;
RAISE LOG 'slow path for %', uid;
RAISE EXCEPTION 'invalid status: %', NEW.status
  USING ERRCODE = '22000', HINT = 'check status column';
-- 捕获
BEGIN
  PERFORM risky_op();
EXCEPTION WHEN unique_violation THEN
  RAISE NOTICE 'duplicate ignored';
END;
#
★★

63. 存储过程是否支持事务控制语句(COMMIT/ROLLBACK)?PostgreSQL 函数中 BEGIN ... EXCEPTION 的事务边界如何?

存储过程是否支持事务控制语句(COMMIT/ROLLBACK)?PostgreSQL 函数中 BEGIN ... EXCEPTION 的事务边界如何?

  • 过程/函数的事务控制能力差异
  • PG 函数内 BEGIN...EXCEPTION 的子事务语义
  • 过程(PROCEDURE)中的显式事务

事务控制能力差异:传统函数(FUNCTION)运行在"调用者事务"内,不允许显式 COMMIT/ROLLBACK(PG 函数中执行 COMMIT 报错 "cannot commit while a subtransaction is active" 或"invalid transaction termination"——函数作为表达式/语句的一部分,其事务边界由外层控制);存储过程(PROCEDURE,PG 11+)允许在过程体内执行 COMMIT/ROLLBACK(过程独立驱动事务,可分批提交),SQL Server 过程内可事务控制(BEGIN TRAN/COMMIT/ROLLBACK)、Oracle 过程运行于会话事务(可用自治事务 PRAGMA AUTONOMOUS_TRANSACTION 隔离)、MySQL 过程内事务语句可用(受 autocommit 与存储引擎限制,函数内禁止显式事务)。

PG 函数中 BEGIN ... EXCEPTION 的事务边界:函数体内的 BEGIN...END EXCEPTION 块创建"子事务(subtransaction)"——块内发生异常时只回滚"该块内的变更",块外的变更保留,异常被捕获后继续执行;语义上类似保存点(SAVEPOINT)包装:BEGIN 前隐式保存点、EXCEPTION 触发时回滚到保存点再执行 WHEN 分支。要点:子事务有额外开销(保存点记录),高频循环内用 EXCEPTION 块性能差;子事务回滚不影响外层事务其他部分;函数整体仍受外层事务控制(函数内无法提交外层);嵌套 EXCEPTION 块形成嵌套子事务。工程建议:批处理用过程(PROCEDURE)内显式 COMMIT 分批发;函数内用 EXCEPTION 块做局部容错(如批量插入中跳过冲突行)但要控制开销。

答题先对比函数(在外层事务内、不可 COMMIT/ROLLBACK)与过程(可显式事务控制)的能力差异及各库情况(Oracle 自治事务、MySQL 限制),再重点讲 PG 函数内 BEGIN...EXCEPTION 的子事务语义(隐式保存点、异常回滚块内变更、开销),最后给分批发与局部容错的工程建议。

-- 过程内显式事务(PG 11+)
CREATE PROCEDURE batch_load() LANGUAGE plpgsql AS $$
BEGIN
  INSERT INTO t SELECT ... FROM src LIMIT 10000;
  COMMIT;
  INSERT INTO t SELECT ... FROM src OFFSET 10000 LIMIT 10000;
  COMMIT;
END $$;
CALL batch_load();
-- 函数内子事务容错
BEGIN
  INSERT INTO t VALUES (NEW.id);
EXCEPTION WHEN unique_violation THEN NULL;  -- 跳过冲突行,块外变更保留
END;
#
★★

64. CREATE FUNCTION 的基本语法(参数、返回类型、函数体语言)示例?

CREATE FUNCTION 的基本语法(参数、返回类型、函数体语言)是什么?请给出示例?

  • 语法结构(参数、RETURNS、LANGUAGE)
  • 函数体的语言选择
  • 稳定性标记与示例

基本语法(PostgreSQL):CREATE [OR REPLACE] FUNCTION 名(参数类型列表) RETURNS 返回类型 AS '函数体' LANGUAGE 语言 [IMMUTABLE|STABLE|VOLATILE] [SECURITY 模式];参数可命名(name int,函数体内引用)、可声明 IN/OUT/INOUT 与 DEFAULT;返回类型支持标量(INT、TEXT)、复合(表名/复合类型)与集合(RETURNS TABLE(...)、SETOF);语言可选 sql、plpgsql、plpython 等(默认 sql)。示例(SQL 语言):CREATE FUNCTION add_one(x int) RETURNS int LANGUAGE SQL IMMUTABLE AS 'SELECT x + 1';;示例(plpgsql):CREATE FUNCTION get_emp(emp_id INT) RETURNS emp AS $$ DECLARE r emp; BEGIN SELECT * INTO r FROM emp WHERE id = emp_id; RETURN r; END $$ LANGUAGE plpgsql STABLE;($$ 美元引用避免转义单引号)。

要点:其一,OR REPLACE 只替换函数体与签名(同名同参才替换,重载按参数区分);其二,稳定性标记(IMMUTABLE/STABLE/VOLATILE)影响索引与优化(默认 VOLATILE);其三,函数体语言与安全(LANGUAGE sql 简单、plpgsql 可过程化,SECURITY DEFINER 提权注意);其四,返回表用 RETURNS TABLE (col type,...) 或 SETOF;其五,DROP FUNCTION 需带签名(DROP FUNCTION f(int))。MySQL 语法:CREATE FUNCTION f(x INT) RETURNS INT DETERMINISTIC RETURN x+1;(DETERMINISTIC 对应稳定性);SQL Server:CREATE FUNCTION dbo.f(@x INT) RETURNS INT AS BEGIN RETURN @x+1 END。

答题先给 PG 的语法骨架(参数/RETURNS/LANGUAGE/稳定性),再给 SQL 与 plpgsql 两个完整示例(含 $$ 引用、INTO 变量),最后补充 OR REPLACE、重载与 DROP 签名、其他库语法对照。

-- PostgreSQL:SQL 语言(可内联)
CREATE FUNCTION add_one(x int) RETURNS int
LANGUAGE SQL IMMUTABLE AS $$ SELECT x + 1 $$;
-- PostgreSQL:plpgsql(过程化)
CREATE FUNCTION get_emp(eid INT) RETURNS emp
LANGUAGE plpgsql STABLE AS $$
DECLARE r emp;
BEGIN
  SELECT * INTO r FROM emp WHERE id = eid;
  RETURN r;
END $$;
-- MySQL
CREATE FUNCTION add_one(x INT) RETURNS INT DETERMINISTIC RETURN x + 1;
#
★★

65. PostgreSQL 中函数能否返回多行结果集?请给出 RETURNS TABLE 与 SETOF 两种语法。

PostgreSQL 中函数能否返回多行结果集?RETURNS TABLE 与 SETOF 两种语法如何写?

  • 集合返回函数(SRF)的概念
  • RETURNS TABLE 语法
  • SETOF 语法与差异

可以。集合返回函数(Set-Returning Function, SRF)返回多行结果集,两种主要语法:其一,RETURNS TABLE (col1 type1, col2 type2, ...)——显式声明结果列结构,函数体内用 RETURN QUERY <查询>(追加查询结果)或 RETURN NEXT(逐行返回变量):CREATE FUNCTION get_items(oid INT) RETURNS TABLE (id INT, name TEXT) LANGUAGE plpgsql AS $$ BEGIN RETURN QUERY SELECT i.id, i.name FROM items i WHERE i.order_id = oid; END $$;;其二,RETURNS SETOF 类型——返回"该类型的行集",类型可为表类型(SETOF emp 返回 emp 表结构的多行)、标量类型(SETOF int 返回一列整数行)、复合类型/枚举:CREATE FUNCTION get_ids() RETURNS SETOF int AS $$ SELECT id FROM emp $$ LANGUAGE SQL;。

差异与细节:RETURNS TABLE 灵活(任意列结构、可含表达式列),SETOF 类型受限于"单一类型/表结构";两者都可在 FROM 中当作派生表使用(SELECT * FROM get_items(1))或与表 JOIN(LATERAL);RETURNS TABLE 中也可用 OUT 参数列(RETURNS TABLE 与 OUT 不能混用?可互转,习惯上 TABLE 更清晰);SQL 语言函数用单个 SELECT 直接返回集合(LANGUAGE SQL 中 RETURN 省略,函数体即 SELECT);plpgsql 用 RETURN QUERY/RETURN NEXT;性能——SQL 语言集合函数可内联(PG 12+ 增强),plpgsql 的 RETURN QUERY 也有流式返回(无整表物化)优化;注意 SETOF 与 RETURNS TABLE 在 pg_proc 中 proretset=true。调用示例:SELECT * FROM fn(); 或 SELECT (fn()).*;(列展开)。迁移对照:SQL Server 内联表值函数(ITVF)对应、MySQL 8.0 函数不能返回结果集(用过程)。

答题先确认"可以返回多行"(SRF),再分别给 RETURNS TABLE(RETURN QUERY/NEXT)与 SETOF(表类型/标量)的语法与示例,最后对比两者差异、FROM 中用法、SQL/plpgsql 的写法与性能提示。

-- RETURNS TABLE
CREATE FUNCTION get_items(oid INT) RETURNS TABLE (id INT, name TEXT)
LANGUAGE plpgsql AS $$
BEGIN
  RETURN QUERY SELECT i.id, i.name FROM items i WHERE i.order_id = oid;
END $$;
-- SETOF 表类型 / 标量
CREATE FUNCTION all_emp() RETURNS SETOF emp LANGUAGE SQL AS $$
  SELECT * FROM emp $$;
CREATE FUNCTION emp_ids() RETURNS SETOF int LANGUAGE SQL AS $$
  SELECT id FROM emp $$;
-- 调用
SELECT * FROM get_items(1);
#
★★

66. 什么是窗口函数(Window Function)?它与聚合函数的区别?

什么是窗口函数(Window Function)?它与聚合函数的区别是什么?

  • 窗口函数的定义与 OVER 子句
  • 与聚合函数的核心区别(行保留)
  • 典型应用

窗口函数:在"窗口(每行对应的行子集,由 OVER 子句的 PARTITION BY/ORDER BY/框架定义)"上计算、且"保留每一行"的函数——每行输出一行,行数不减少;聚合函数(SUM/COUNT/AVG 等)在 GROUP BY 分组上计算,"每组输出一行",行被归约。核心区别:其一,行数——窗口函数输出行数 = 输入行数(每行都能看到窗口值),聚合输出行数 = 组数(GROUP BY 后行减少);其二,作用范围——窗口函数用 OVER (PARTITION BY ...) 定义窗口(可按组、可排序、可限框架),聚合用 GROUP BY 定义组;其三,组合——聚合函数可作为窗口函数使用(SUM(x) OVER (...) 在窗口内求和),窗口专用函数(ROW_NUMBER/RANK/LAG/LEAD/FIRST_VALUE)只能作窗口函数;其四,求值时机——窗口函数在聚合之后、SELECT 投影阶段计算(WHERE/GROUP BY 后),因此 WHERE 中不能用窗口函数。

典型应用:排名(ROW_NUMBER/RANK/DENSE_RANK/NTILE)、行间引用(LAG/LEAD 取前后行、FIRST_VALUE/LAST_VALUE 取窗口边界值)、移动计算(SUM/AVG OVER 带 ROWS 框架做移动合计/移动平均)、分组内占比(SUM(x) OVER (PARTITION BY g) / 全局 SUM)、去重保留首行(ROW_NUMBER 配子查询)。记忆要点:窗口函数是"在保留行的前提下附加计算列",聚合是"归约行数";两者可嵌套(窗口内套聚合:AVG(SUM(x)) OVER (...),需先 GROUP BY)。性能:窗口函数避免"自连接取前后行"(LAG/LEAD 替代 JOIN)、避免多次聚合;注意大窗口的排序开销与框架语义(ROWS vs RANGE)。

答题先定义窗口函数(OVER 定义窗口、每行输出一行),再与聚合函数对比核心区别(行保留 vs 行归约、作用范围、求值时机),最后列典型应用(排名、行间引用、移动计算、占比)与性能收益。

-- 窗口函数:保留每行并附排名/占比
SELECT name, dept, salary,
       ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn,
       SUM(salary) OVER (PARTITION BY dept) AS dept_total
FROM emp;
-- 聚合:行被归约
SELECT dept, SUM(salary) FROM emp GROUP BY dept;
#
★★

67. 函数体内能否使用动态 SQL(EXECUTE)?

函数体内能否使用动态 SQL(EXECUTE)?各数据库的支持与限制是什么?

  • 动态 SQL 的用途
  • 各库语法(EXECUTE、EXEC、sp_executesql、PREPARE)
  • 注入风险与性能

可以,各库都支持函数/过程内动态 SQL(运行时拼接并执行的 SQL):PostgreSQL——plpgsql 中 EXECUTE 'SELECT ... FROM ' || table_name || ' WHERE id = $1' USING p_id(USING 传参避免拼接注入;动态 SQL 每次执行都重新规划(无计划缓存,性能差于静态语句,但适合表名/列名动态的场景);LANGUAGE SQL 函数不能动态执行(无 EXECUTE)。SQL Server——动态 SQL 常用 EXEC(@sql) 与 sp_executesql @sql, N'@id INT', @id(sp_executesql 支持参数化、可复用计划、防注入,优于字符串拼接);MySQL——PREPARE/EXECUTE/DEALLOCATE 或过程内 CONCAT 拼接后 PREPARE(存储过程/函数内可用,注意 SQL 模式与权限);Oracle——EXECUTE IMMEDIATE sql_string [USING ...](原生支持,动态 SQL 在 PL/SQL 中常用))。

注意事项:其一,注入风险——拼接用户输入到动态 SQL 是 SQL 注入主通道,必须参数化(USING、sp_executesql 参数、绑定变量)或白名单校验(动态表名/列名无法参数化,用白名单/正则校验);其二,性能——PG 动态 SQL 每次重新规划(可用 PREPARE+EXECUTE 手动缓存)、SQL Server sp_executesql 可复用缓存计划、MySQL PREPARE 有语句缓存;高频路径避免动态 SQL;其三,权限——动态 SQL 以定义者/调用者身份执行取决于 SECURITY 设置;其四,可读性与审计——动态 SQL 使代码难读、难以静态检查(EXPLAIN 无法直接分析),注释与日志要保留生成的 SQL;其五,SQL 注入防护补充:QUOTE_LITERAL/QUOTE_IDENT(PG)在必须拼接时转义。工程规范:优先静态 SQL;动态场景必须参数化;动态标识符用白名单;生产审计动态 SQL 生成内容。

答题先确认支持并逐库给语法(PG EXECUTE...USING、SQL Server sp_executesql、MySQL PREPARE、Oracle EXECUTE IMMEDIATE),再重点讲注入风险与参数化防护、性能(重新规划 vs 计划缓存)、权限与可读性,最后给工程规范。

-- PostgreSQL:参数化动态 SQL
EXECUTE 'SELECT * FROM ' || quote_ident(tbl) || ' WHERE id = $1' USING p_id;
-- SQL Server:sp_executesql 参数化
EXEC sp_executesql N'SELECT * FROM t WHERE id = @id', N'@id INT', @id = 1;
-- MySQL
PREPARE stmt FROM 'SELECT * FROM t WHERE id = ?'; EXECUTE stmt USING @id;
#
★★

68. 函数对查询优化的影响(IMMUTABLE 内联、VOLATILE 阻止缓存)?

函数对查询优化有哪些影响?IMMUTABLE 内联与 VOLATILE 阻止缓存的具体机制是什么?

  • 稳定性标记的优化含义
  • IMMUTABLE 的内联与常量折叠
  • VOLATILE 对索引/缓存/并行的阻止

函数稳定性标记(IMMUTABLE/STABLE/VOLATILE)直接决定优化器能做什么:IMMUTABLE(不可变)——相同输入恒同输出、无时间/会话依赖:优化器可做常量折叠(WHERE immut_func(2) = 4 编译期求值)、可建表达式索引(CREATE INDEX ON t (immut_col(x)))、可参与物化视图与生成列(要求 IMMUTABLE)、SQL 函数可内联展开(调用处替换为函数体,谓词下推与索引利用成为可能)、缓存/重复使用结果安全;STABLE——单语句内结果稳定(如 now() 在语句内固定):可建表达式索引(PG 中 STABLE 允许)、不能常量折叠跨语句,但语句内可复用;VOLATILE——每次调用可能不同(random()、clock_timestamp()、含副作用函数):优化器禁止其用于表达式索引与生成列、阻止常量折叠、阻止"只执行一次"的优化(WHERE 中每行求值)、可能阻断某些并行与物化(volatile 函数在物化视图刷新、部分预计算中受限)、阻止函数结果缓存;内联也要求非 volatile(内联会改变调用次数语义)。

错误标注的危害:把 VOLATILE 标成 IMMUTABLE——表达式索引与物化视图数据错(索引内容随时间/会话变化,查询结果错误,最严重事故);把 IMMUTABLE 标成 VOLATILE——丧失优化机会(索引不能用、折叠不做)性能损失。其他影响:函数的 SQL 内联(LANGUAGE SQL + IMMUTABLE/STABLE + 简单体)是把函数开销降到零的关键手段;函数内访问表的函数默认 VOLATILE(PG 不会自动识别),需人工标注 STABLE 才能用于索引;调用开销(每行调用)与执行计划(函数作为过滤器无法下推)也属函数优化影响范畴。

答题先定义三档稳定性的语义,再分别讲 IMMUTABLE 的优化收益(折叠、表达式索引、内联、物化)与 VOLATILE 的阻止面(索引、折叠、缓存、并行),最后重点警示错误标注的方向性危害与"访问表函数需人工标 STABLE"的细节。

#
★★

69. 函数式编程风格的 SQL(如函数管道)可行吗?

函数式编程风格的 SQL(如函数管道)可行吗?SQL 的函数式表达有哪些形式与边界?

  • SQL 的声明式与函数式特征
  • 函数管道(CTE 链、窗口、JSON 函数)的形式
  • 与真正函数式语言(map/filter/reduce)的差距

可行但有限:SQL 本身是"基于集合的声明式语言",天然具备函数式要素——无副作用(查询不修改状态)、表达式组合、高阶抽象(GROUP BY 即 reduce、WHERE 即 filter、SELECT 即 map、JOIN 即 zip/flatMap 的近似),因此"函数式风格"的查询(把数据处理写成表达式链)完全可行且是主流写法:CTE 管道(WITH 把多步变换串成流水线,类似管道操作符)、窗口函数链(每行附加计算列)、JSON/数组函数管道(jsonb 转换链)、标量函数组合(UPPER(TRIM(x)))。聚合即 fold/reduce(SUM/COUNT/STRING_AGG),映射即投影,过滤即谓词——这些与函数式编程的 map/filter/reduce 一一对应。

边界与差距:其一,SQL 无"一等函数"(不能把函数作为参数传递、无高阶函数,函数管道只能靠 CTE/子查询"数据流"而非"函数组合");其二,副作用与可变状态——SQL 的 DML 是命令式(UPDATE 修改状态),纯查询才函数式;其三,惰性求值与柯里化等函数式特性缺失(求值由优化器决定);其四,递归能力有限(递归 CTE 表达不动点,但表达力弱于通用函数式语言);其五,性能——函数式"优雅链"可能掩盖执行代价(每层 CTE 物化、函数每行调用),EXPLAIN 验证必要。结论:函数式风格(声明式、无副作用、组合化)是 SQL 的推荐用法(尤其分析查询),但本质是"声明式查询语言",不能完全套用函数式编程范式(高阶函数、惰性求值),复杂逻辑仍靠 CTE/视图/过程组织。

答题先论证 SQL 的函数式要素(filter/map/reduce 对应 WHERE/SELECT/GROUP BY、无副作用、表达式组合)与函数管道形式(CTE 链、窗口链、JSON 管道),再列边界(无一等函数/高阶函数、DML 有副作用、无惰性求值),最后给"声明式组合是主流,但别套用完整函数式范式"的结论。

#
★★

70. 函数能否修改表数据(INSERT/UPDATE/DELETE)?

函数能否修改表数据(INSERT/UPDATE/DELETE)?各数据库的限制是什么?

  • 函数内 DML 的支持情况
  • SQL 标准与各库差异(函数无副作用约束)
  • 事务与递归注意点

可以,但语义与限制因库而异:PostgreSQL——plpgsql 函数内可执行 INSERT/UPDATE/DELETE(DML 属于函数体语句,运行在调用事务内),且配合 RETURNING 可做"修改后返回"的封装;但函数内不能执行事务控制(COMMIT/ROLLBACK)与某些 DDL(PG 14 前函数内 DDL 受限制,14+ 允许大多数 DDL),纯 SQL 函数(LANGUAGE SQL)内也可写 DML(单语句),SQL 函数内联要求无副作用(含 DML 的 SQL 函数不内联);MySQL——存储函数内允许 DML(但禁止显式事务语句与部分语句如 LOAD DATA),存储过程随意;SQL Server——函数(UDF)禁止 DML("Invalid use of a side-effecting operator"——UDF 不能修改数据库状态,修改表只能用存储过程),这是 SQL Server 最严格的限制;Oracle——PL/SQL 函数内可 DML(但 SQL 上下文中调用的函数有纯度限制:DML 函数不能在 SELECT 中调用,需自治事务或过程)。

注意点:其一,副作用与调用上下文——在 SELECT 中调用含 DML 的函数(如 SELECT f() 中执行 INSERT)是反模式(每行调用多次写入、结果不确定),应明确禁止(用过程/触发器替代);其二,事务边界——函数内 DML 与调用方同事务(回滚一起回滚),异常时子事务块可局部回滚;其三,递归与触发——函数内 DML 可能触发触发器/级联,注意递归;其四,性能与并发——函数内高频 DML 有锁与日志开销。工程结论:修改数据的功能优先用存储过程/应用层事务,函数只做"带返回值的封装"(如 UPSERT 后返回新 id 的函数),并避免在 SELECT 表达式位置调用有副作用函数。

答题先按库列 DML 支持(PG/MySQL/Oracle 函数内可 DML 但有限制、SQL Server UDF 全面禁止用过程),再讲事务边界(同外层事务)、SELECT 中调用副作用函数的反模式、递归触发注意点,最后给工程建议。

-- PostgreSQL:函数内 DML + RETURNING
CREATE FUNCTION touch_t(id INT) RETURNS timestamptz
LANGUAGE plpgsql AS $$
BEGIN
  UPDATE t SET updated_at = now() WHERE id = touch_t.id RETURNING updated_at INTO v;
  RETURN v;
END $$;
-- SQL Server:UDF 内 DML 报错,需改为存储过程
#
★★

71. 存储过程(PROCEDURE)与函数(FUNCTION)在调用语法上的差异?CALL vs SELECT?

存储过程(PROCEDURE)与函数(FUNCTION)在调用语法上的差异是什么?CALL 与 SELECT 的区别是什么?

  • 函数用 SELECT/表达式调用、过程用 CALL
  • 返回值与结果集的位置
  • 各库调用语法差异

调用语法差异(标准语义):函数——在表达式中调用(SELECT func(args)、SELECT * FROM func_table(args)(表函数在 FROM)、赋值/条件中),返回值直接参与表达式;过程——用 CALL proc(args)(标准,PG 11+ 引入 CALL;SQL Server 用 EXEC proc(EXECUTE),MySQL 用 CALL proc)。差异根源:函数是"表达式语义"(有返回值、可嵌套在任何表达式中),过程是"语句语义"(执行操作,无表达式位置返回值,数据经 OUT 参数/结果集传出)。CALL vs SELECT 的区别:SELECT 用于"查询并返回结果集/值",可出现在表达式与 FROM 中(函数);CALL 是独立语句,专用于"执行过程",不返回行集(若有结果集由 OUT 游标/多结果集返回,客户端需额外读取),CALL 后可带参数(IN/OUT 实参),CALL 不能嵌在表达式中。

各库细节:PostgreSQL——函数 SELECT f(...) 或表达式、表函数 FROM f(...);过程 CALL p(...)(11+,CALL 支持事务控制过程);MySQL——函数 SELECT f() 表达式,过程 CALL p()(CALL 后可带 OUT 变量 @x 接收出参);SQL Server——函数 SELECT dbo.f()、FROM 表值函数,过程 EXEC p @p1=1(EXEC 也可执行函数与动态 SQL);Oracle——函数 SELECT f() FROM dual 或 PL/SQL 中 v := f(),过程 EXECUTE/CALL p()(EXEC 是 SQL*Plus 命令,CALL 是 SQL 语句)。注意:函数调用也可能产生结果集(表函数),过程也可能无参数;跨库迁移时把"过程调用"统一为 CALL/EXEC、函数统一为表达式/表引用。

答题先讲标准语义(函数表达式调用 vs 过程 CALL 语句、返回值位置),再对比 CALL 与 SELECT(查询返回 vs 执行操作、可嵌性、OUT 接收),最后逐库列细节(PG 11+ CALL、SQL Server EXEC、MySQL CALL @out、Oracle EXEC/CALL)与迁移建议。

-- 函数:表达式/表位置调用
SELECT add_one(5);
SELECT * FROM get_items(1);
-- 过程:CALL(PG 11+ / MySQL)
CALL batch_load();
-- SQL Server
EXEC batch_load @limit = 100;
#
★★

72. 递归函数(RECURSIVE)的实现原理是什么?

递归函数(RECURSIVE)的实现原理是什么?SQL/数据库中的递归如何表达与执行?

  • 递归函数的标准原理(基线+递推)
  • 递归 CTE 的执行机制(工作队列)
  • 终止条件与性能

递归函数原理(通用):递归 = 基线条件(base case,直接返回、终止)+ 递归步骤(把问题缩小为同型子问题,调用自身),执行靠"调用栈"逐层展开、逐层返回。SQL 中的递归表达是"递归 CTE"(WITH RECURSIVE),原理对应:锚点查询(anchor)提供基线行集(初始结果),递归查询(recursive term)引用 CTE 自身把"上一轮新增的行"扩展出新行,两者 UNION [ALL] 合并,反复迭代直到"本轮不再产生新行"(不动点)——不依赖调用栈,而是"迭代式工作队列":每轮把上轮结果作为输入再查一次,等价于函数递归的"递推展开"。示例:树遍历(先取根,再按 parent 逐层取子)、传递闭包(可达性)、数字序列。

执行细节:数据库用"工作表(working table)+ 中间表"循环:把锚点结果放入结果集与工作表,每轮"工作表 JOIN 递归查询"产生新行(未出现过的加入结果,UNION 去重防环;UNION ALL 不去重需深度计数或访问集合防环),直到工作表为空;递归次数有限制(SQL Server 默认 MAXRECURSION 100 层、MySQL cte_max_recursion_depth 默认 1000、PG 默认可变(PG 14 前默认 1000 行工作区限制))。性能与陷阱:递归层数深、每层全量扫描(无索引)时指数爆炸;环形数据(树中环)导致死循环(UNION 去重可终止、UNION ALL 必须显式深度上限);相关子查询不能引用递归 CTE 的某些用法;优化建议——递归部分尽量用索引(JOIN 键建索引)、设置深度上限(WHERE depth < N)、用 UNION 或访问路径列防环。通用语言中递归函数(如 PL/pgSQL 函数自调用)原理相同(基线+递推+栈),但数据库更常用递归 CTE 表达"集合级递归"。

答题先讲递归函数的一般原理(基线+递推+调用栈),再重点讲递归 CTE 的执行机制(锚点+递归项+工作队列迭代到不动点)与防环/深度限制,最后列性能陷阱(深递归、环、索引)与优化建议。

WITH RECURSIVE nums AS (
  SELECT 1 AS n
  UNION ALL
  SELECT n + 1 FROM nums WHERE n < 10   -- 递推 + 终止条件
) SELECT * FROM nums;
-- 树遍历(带深度与防环)
WITH RECURSIVE tree AS (
  SELECT id, parent_id, 1 AS depth FROM category WHERE parent_id IS NULL
  UNION ALL
  SELECT c.id, c.parent_id, t.depth + 1
  FROM category c JOIN tree t ON c.parent_id = t.id
  WHERE t.depth < 20
) SELECT * FROM tree;
#
★★

73. 强实体(Strong Entity)与弱实体(Weak Entity)的区别,弱实体的标识符依赖父实体的部分键如何建模?

强实体(Strong Entity)与弱实体(Weak Entity)的区别是什么?弱实体的标识符依赖父实体部分键时如何建模?

  • 强/弱实体的定义与依赖关系
  • 弱实体的部分键(Partial Key)与存在依赖
  • 关系模型中的建模(复合主键、ON DELETE CASCADE)

区别:强实体(Strong Entity)有"独立存在"与"自身完整标识符"(自己的主键),不依赖其他实体;弱实体(Weak Entity)依赖强实体(所有者/父实体)而存在——离开父实体无意义(存在依赖,existence dependency),其标识符必须借助父实体的主键(自身只有部分键/判别符,discriminator)。典型:订单明细是订单的弱实体(明细离开订单无意义,标识 = 订单号 + 行号);房间是酒店的弱实体(标识 = 酒店编号 + 房号);亲属关系中的"子女"依赖"父母"。

建模:ER 中弱实体用双矩形、部分键用虚线椭圆、识别联系用双菱形;关系模型中弱实体建成"子表",主键 = 父表主键 + 部分键(复合主键),外键引用父表主键并通常配 ON DELETE CASCADE(父实体删除时弱实体随之删除,符合存在依赖语义);父表主键也常设为子表的复合主键一部分(如 order_items(order_id, line_no),order_id 既是外键也是主键一部分)。注意:部分键只在"父实体范围内"唯一(line_no 在每个订单内从 1 开始),因此必须复合主键;若业务上弱实体可全局唯一(如明细有全局 id),则退化为强实体建模(仍可保留逻辑依赖)。工程要点:复合主键的级联删除、外键列做索引、以及弱实体数量大时考虑分区(按父 id 分区)。

答题先定义强/弱实体(独立存在与独立标识 vs 存在依赖与部分键),再讲弱实体的"父主键+部分键"复合主键建模与 ON DELETE CASCADE 语义,最后给 ER 符号(双矩形/虚线椭圆/双菱形)与工程细节。

CREATE TABLE orders (id INT PRIMARY KEY);
CREATE TABLE order_items (
  order_id INT NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
  line_no  INT NOT NULL,
  product  TEXT,
  PRIMARY KEY (order_id, line_no)   -- 父主键 + 部分键
);
#

74. ISA 继承关系(Is-A Hierarchy)的三种实现策略(单表、类表、具体表)各自的取舍是什么?

ISA 继承关系(Is-A Hierarchy)的三种实现策略——单表(Single Table)、类表(Class Table)、具体表(Concrete Table)——各自的取舍是什么?

  • 三种继承映射策略的结构
  • 各自的查询/写入/约束优劣
  • 选型依据

三种策略(关系模型中表达"父类-子类"):单表继承(Single Table,STI)——所有父子属性放一张大表,加类型判别列(type/kind);优点:查询简单(单表扫描、无 JOIN)、写入单表、外键简单;缺点:列冗余(子类特有列在其他行 NULL)、约束弱(无法保证"该行类型必须填对应列")、宽表;适合子类差异小、查询以父类为主的场景。类表继承(Class Table,CTI,又称 JOINED)——父表 + 每子类一张子表(子表主键同父表主键并外键引用),查询按类型 JOIN;优点:列按类型精准分布(无 NULL 冗余)、约束清晰(子表可加 CHECK)、规范化;缺点:查询需 JOIN(跨类型查询 UNION)、写入涉及多表(需事务)、外键引用子类型时复杂;适合子类差异大、需精确类型约束。具体表继承(Concrete Table,TPC)——每个子类一张完整表(重复父类列),无父表;优点:查询单表最快、无 JOIN、列独立演化;缺点:父类层面的统一查询必须 UNION ALL 多表、外键引用无法指向"父类型"(重复列导致引用分散)、父类约束/默认值无法集中;适合子类查询完全独立、几乎不做父类聚合的场景。

选型依据:类型间共享查询频率(高 → 单表或类表)、子类差异度(大 → 类表/具体表)、约束强度需求(强 → 类表 + CHECK)、写入/读取比、以及 ORM 支持(Hibernate 的 SINGLE_TABLE/JOINED/TABLE_PER_CLASS 映射对应)。工程建议:多数业务用单表 + type 列(简单)或类表(规范),具体表用于隔离强、聚合少的场景;注意单表的 NOT NULL 约束对子类列要放宽(NULL 表示不适用)。

答题先定义三种策略的结构(判别列大表 / 父表+子表 JOIN / 每类独立全表),再逐个讲优劣(查询、写入、约束、外键),最后给按共享查询频率与差异度的选型建议与 ORM 对应。

#

75. 多值属性(Multivalued Attribute)的规范化处理,为何必须拆为独立实体?

多值属性(Multivalued Attribute)的规范化处理是什么?为什么必须拆为独立实体?

  • 多值属性的定义与 1NF 冲突
  • 拆为独立实体的建模方法
  • 不拆分的问题

多值属性:一个属性可同时取多个值(如员工的多个电话、产品的多个标签),直接存单列违反 1NF(属性必须原子)——若用逗号分隔字符串("138x,139y")或数组/JSON 存储,则:其一,查询该属性的单个值需要字符串解析/JSON 展开(函数包裹、无法索引、性能差);其二,更新单个值要重写整列(并发写冲突、丢失更新);其三,统计与关联(按电话查员工、电话唯一约束)无法表达;其四,唯一性、外键等完整性无法按值保证。因此规范化要求把多值属性拆为"独立实体"(子表):员工-电话表(employee_phone(emp_id, phone)),每值一行,满足 1NF,支持按值查询(索引)、唯一约束(同一员工电话唯一)、独立增删。

建模方式:拆成 1:N 子表(员工 1:N 电话),子表主键 = 父外键 + 序号/值;若"值"需要共享(标签被多实体引用)则拆成"实体 + 关联表"(M:N:产品-标签表)。ER 图中多值属性用双椭圆表示,转关系模型时展开成子表。现代数据库的数组/JSON 列(PostgreSQL 数组、JSONB)提供了"多值存储"的替代:适合"整体读写、不按单个值查询/约束"的场景(如一次性读取的配置列表),但丧失按值查询、唯一与关联能力;工程取舍:多值需要查询/约束/关联 → 拆表;仅整体存取 → JSON/数组列可接受(明确权衡)。注意:PostgreSQL 数组虽支持索引(GIN)与包含查询,但跨表关联与唯一仍不便,且更新数组仍重写整列。

答题先定义多值属性与 1NF 冲突(字符串/数组存储的问题:解析查询、整列重写、无按值约束),再讲规范化拆分(1:N 子表或 M:N 关联表)与 ER 双椭圆符号,最后对比现代 JSON/数组列的适用边界与取舍结论。

-- 反模式:多值存字符串/数组
CREATE TABLE emp (id INT PRIMARY KEY, phones TEXT);  -- '138x,139y'
-- 规范化:拆为子表
CREATE TABLE emp_phone (
  emp_id INT NOT NULL REFERENCES emp(id) ON DELETE CASCADE,
  phone  VARCHAR(20) NOT NULL,
  PRIMARY KEY (emp_id, phone)
);
#

76. Chen 记法中菱形代表什么?椭圆代表什么?矩形代表什么?

Chen 记法中菱形、椭圆、矩形分别代表什么?其他符号的含义是什么?

  • 矩形=实体、椭圆=属性、菱形=联系
  • 键/多值/派生属性的特殊符号
  • 基数的标注方式

Chen 记法核心符号:矩形(Rectangle)——实体(Entity);椭圆(Ellipse)——属性(Attribute),实体与属性用直线连接;菱形(Diamond)——联系(Relationship),连接相关实体(连线数量=参与实体数,二元联系为三角形结构),联系可带自己的属性(连线到椭圆)。特殊符号:主键属性——椭圆内文字加下划线;部分键(弱实体判别符)——虚线椭圆+下划线(或虚下划线);多值属性——双椭圆(内外两层);派生属性(可计算,如年龄)——虚线椭圆;弱实体——双矩形;识别联系(弱实体依赖的联系)——双菱形。基数标注:在联系与实体的连线上标 1、N、M(1:N、M:N、1:1),或 min-max 计数(如 (0,*))。

用途:Chen 记法是教学与理论的标准符号(1976 年提出),强调"概念建模"(实体-联系-属性的完整语义),适合画 ER 图用于数据库设计文档;相比 Crow's Foot(工程向),Chen 更显式表达"联系"与"属性化联系"。转换:ER 图转关系模型时——实体转表、属性转列、联系按基数转外键/关联表(1:N 在 N 侧加外键、M:N 建连接表)。注意:现代工具(PowerDesigner 等)默认 Crow's Foot,Chen 记法主要用于教材与规范图。

答题先给出三个核心符号的对应(矩形实体、椭圆属性、菱形联系)并说明连线结构,再列特殊符号(下划线主键、双椭圆多值、虚线椭圆派生、双矩形弱实体、双菱形识别联系)与基数标注,最后讲用途与转换关系模型的方法。

#

77. ER 图中 1:N、N:M、1:1 三种关系如何用图形符号区分?

ER 图中 1:N、N:M、1:1 三种关系如何用图形符号区分?

  • 联系线上基数的标注
  • Chen 与 Crow's Foot 的表示差异
  • 三类关系转关系模型的规则

表示方式:Chen 记法——联系菱形与实体间的连线上标注基数:1:N 写 "1" 与 "N"(或 M,表示多)、M:N 写 "M" 与 "N"、1:1 两侧都写 "1";更精确可用 (min,max) 计数(如 (1,1) 表示恰好一个、(0,N) 表示零到多)。Crow's Foot——不用数字,用连线靠近实体端的"脚"符号:单竖线=恰好一个、鸡爪(三叉)=多个、圆 O=零、方括号/竖线组合表达最小基数(O| 表示 0..1、|| 表示 1..1、O< 表示 0..N、|< 表示 1..N),1:N 表现为"一侧单竖线、另一侧鸡爪",M:N 表现为"两侧都鸡爪"(需连接表),1:1 为"两侧单竖线"。三者的区分本质是"两端的最大基数(1 或多)"与可选性(0 或 1)的组合。

转关系模型规则:1:1——任一(或两侧)表加对方主键作外键(并加唯一约束);1:N——在 N 侧表加对方主键作外键;M:N——必须新建连接表(junction table,含双方外键与关系属性,复合主键)。注意区分"关系类型"与"实现":M:N 在物理表上是两个 1:N(通过连接表);符号层面(逻辑 ER)区分清楚后转换自然。工程建议:ER 图先标基数(min-max 最严谨),实现时按规则落表,M:N 的连接表命名与复合主键提前设计。

答题先分别讲 Chen(数字 1/N/M 或 min-max)与 Crow's Foot(竖线/鸡爪/O 组合)的基数符号,再明确三类关系的判别(最大基数与可选性)与转关系模型的规则(1:1 加外键唯一、1:N 在 N 侧加外键、M:N 建连接表),最后给建模建议。

#

78. COMMENT ON TABLE 的用途是什么?

COMMENT ON TABLE 的用途是什么?如何添加与查询表/列注释?

  • 注释对象的用途(文档化、数据字典)
  • COMMENT ON 语法(表/列/约束/索引)
  • 注释的存储与查询

用途:把业务说明、负责人、口径定义等"文档"写入数据库对象元数据(数据字典),替代散落的文档——表注释说明表含义与数据口径(如"订单主表,status 取值见字典表")、列注释说明字段业务语义(如"金额,单位分")、约束/索引/视图/函数也可注释;好处:与对象同生命周期(迁移/导出随 DDL 走)、BI 工具与 ORM 可读取展示、数据字典自动生成、多人协作时"就近文档"。语法:COMMENT ON TABLE t IS '订单表';COMMENT ON COLUMN t.amount IS '金额(单位:分)';COMMENT ON VIEW/CONSTRAINT/INDEX/COLUMN/SCHEMA 等对象均可(PG/Oracle/MySQL 8.0 支持表/列注释(MySQL 建表时 COMMENT 或 ALTER TABLE COMMENT/COLUMN COMMENT);SQL Server 用扩展属性 sp_addextendedproperty)。查询:PostgreSQL——\d+ t(psql 显示注释)、obj_description('t'::regclass)、col_description('t'::regclass, 列号)、pg_description 表;MySQL——SHOW FULL COLUMNS FROM t、information_schema.columns.COLUMN_COMMENT、tables.TABLE_COMMENT;Oracle——USER_TAB_COMMENTS/USER_COL_COMMENTS。

注意点:注释是"文档不是约束"(不校验、不影响执行),但自动化(导出、字典、迁移比对)会依赖;迁移工具(Flyway/Liquibase)与 ORM 可自动生成注释;注释中的引号转义(单引号双写);删除注释用 IS NULL:COMMENT ON TABLE t IS NULL。工程建议:为每张表与关键列写注释(表:用途+口径+owner;列:单位+枚举含义+更新时机),纳入代码评审与 CI 检查(无注释的表/列告警)。

答题先讲注释的用途(对象内文档、数据字典、随 DDL 迁移、工具读取),再给 COMMENT ON 语法(表/列/其他对象、清空用 IS NULL)与三库实现差异(PG/Oracle/MySQL 原生、SQL Server 扩展属性),最后讲查询方式与工程规范。

COMMENT ON TABLE orders IS '订单主表,status 取值见 dict 表';
COMMENT ON COLUMN orders.amount IS '订单金额,单位分';
COMMENT ON TABLE orders IS NULL;  -- 删除注释
-- 查询(PostgreSQL)
SELECT obj_description('orders'::regclass), col_description('orders'::regclass, 1);
-- 查询(MySQL)
SELECT TABLE_COMMENT, COLUMN_COMMENT FROM information_schema.tables t
JOIN information_schema.columns c ON ...
WHERE t.TABLE_NAME = 'orders';
#

79. DROP TABLE 与 TRUNCATE TABLE 的差异是什么?

DROP TABLE 与 TRUNCATE TABLE 的差异是什么?各自适用场景是什么?

  • 删除对象 vs 清空数据
  • 事务性与回滚差异
  • 触发器/日志/权限差异

本质差异:DROP TABLE 删除"整个表对象"(结构+数据+索引+约束+依赖元数据,之后表不存在,需重新 CREATE);TRUNCATE TABLE 只清空"表内全部数据"(结构、索引、约束保留,表仍可用,通常配合 RESTART IDENTITY 重置自增)。行为差异:其一,事务性——PG 中 TRUNCATE 是事务性 DDL(可回滚)、SQL Server 的 TRUNCATE 也可回滚(日志记录页释放)、MySQL 中 TRUNCATE 与 DROP 同属 DDL(隐式提交、不可回滚,文档明确 "Truncate operations cause an implicit commit, and so cannot be rolled back");DROP 在 PG 中可回滚(DDL 事务性)、MySQL 中 DROP 隐式提交不可回滚;其二,速度与日志——TRUNCATE 按"释放页"实现(记录极少日志、快),DROP 删对象同样快(删元数据与文件);都远快于 DELETE(逐行+触发器+日志);其三,触发器——TRUNCATE 不触发行级 DELETE 触发器(PG 只触发语句级 TRUNCATE 触发器、MySQL 完全不触发 DELETE 触发器、SQL Server 不触发 DELETE 触发器),DELETE 触发行级触发器;其四,权限——DROP 需要表 owner/超级用户、TRUNCATE 在 PG 中需要表 owner(MySQL 需要 DROP 权限);其五,锁——两者都需排他锁,TRUNCATE 期间表不可访问;其六,外键——被外键引用的表 TRUNCATE 受限(PG 报错需 CASCADE 且仍有风险、MySQL 有引用时不允许 TRUNCATE、SQL Server 类似),DROP 时外键依赖需 CASCADE 处理。

适用场景:清空测试表/临时表数据(保留结构)用 TRUNCATE(快、重置自增);彻底删除表(重建结构或归档迁移)用 DROP;注意 TRUNCATE 不能带 WHERE(清全部),需部分删除用 DELETE;生产 DROP 前确认备份与依赖(外键/视图/存储过程),低峰执行。

答题先讲本质差异(删对象 vs 清数据),再按事务性、日志速度、触发器、权限、锁、外键六个维度对比,最后给场景建议(清空用 TRUNCATE、彻底删除用 DROP、部分删除用 DELETE)与生产注意。

#

80. 为什么在生产环境不推荐 DROP TABLE 而推荐 ALTER TABLE RENAME?

为什么在生产环境不推荐 DROP TABLE 而推荐 ALTER TABLE RENAME?两种方式的取舍是什么?

  • DROP 的不可逆与依赖影响
  • RENAME 的"逻辑删除+灰度"策略
  • 回滚与安全窗口

原因:DROP TABLE 在生产是"高风险不可逆操作"——其一,立即物理删除表与数据(PG 中可回滚但会话/脚本一旦提交即不可逆,MySQL 隐式提交无回滚),误删恢复依赖备份(备份窗口内数据丢失);其二,依赖连锁——引用该表的视图、外键、存储过程、物化视图、BI 报表全部失效或报错(RESTRICT 报错、CASCADE 静默连删);其三,无缓冲期——应用代码(ORM 查询、报表 SQL)在表消失瞬间全部报错,无法灰度。推荐 ALTER TABLE t RENAME TO t_archived_20240101 的原因:其一,"逻辑删除"——表还在(改名),数据不丢,应用错误可快速改回(回滚秒级);其二,灰度与缓冲——先改名隔离,观察一段时间(应用报错率、对账),确认无依赖后再真正 DROP(或保留归档);其三,保留审计痕迹——归档表名含时间,数据可回溯。

取舍与流程:低风险场景(确定无引用、有备份、可接受瞬时不可用)可直接 DROP;生产推荐流程:先 RENAME 归档(保留)→ 检查依赖(pg_depend/information_schema 查引用)→ 观察窗口(天级)→ 确认后 DROP(或长期保留压缩归档);配套措施——DROP 前必须备份(pg_dump/物理备份验证可恢复)、低峰执行、事务包裹(PG)、设置锁等待超时(lock_timeout)防阻塞、操作审计。注意 RENAME 不是万能(重命名后原应用代码仍可能误连到新名,需同步改应用或加视图兼容(RENAME 后建同名视图转发?可行:旧名视图指向新表),也可用"建视图兼容旧名"缓解)。

答题先列 DROP 的三类风险(不可逆、依赖连锁、无灰度),再讲 RENAME 的逻辑删除策略(改名归档、快速回滚、观察窗口、审计保留),最后给生产删除的标准流程(备份→RENAME→查依赖→观察→DROP)与配套措施。

-- 安全删除流程:先改名归档
ALTER TABLE orders RENAME TO orders_archived_20260101;
-- 检查依赖
SELECT dependent.relname FROM pg_depend d
JOIN pg_class dependent ON dependent.oid = d.objid
JOIN pg_class source ON source.oid = d.refobjid
WHERE source.relname = 'orders_archived_20260101';
-- 观察期后真正删除
DROP TABLE orders_archived_20260101;
#

81. 表的存储参数(storage parameters)举例有哪些?

PostgreSQL 中表的存储参数(storage parameters)有哪些?各自的含义与作用是什么?

  • 常用存储参数(fillfactor、toast_tuple_target、autovacuum 族)
  • 设置语法与查看
  • 与物理存储的关系

PostgreSQL 表级存储参数(ALTER TABLE ... SET (...) 设置、reloptions 存储):fillfactor——页填充率(默认 100),预留页内空间减少页分裂与 HOT 失效,高频更新表设 70-90;toast_tuple_target——触发 TOAST 的阈值(默认 128 字节提示,实际约 2KB 触发),调小更早压缩/外存、调大减少大字段解压;autovacuum 族——autovacuum_enabled(false 关闭表级自动清理,配合手动 VACUUM)、autovacuum_vacuum_threshold/scale_factor(触发清理的行数阈值与比例)、autovacuum_vacuum_cost_limit(后台清理的 IO 成本上限,控制对在线业务的影响)、autovacuum_vacuum_cost_delay、autovacuum_analyze_threshold/scale_factor(统计信息更新触发);autovacuum_freeze_max_age 相关(事务回卷防护阈值,表级可覆盖)。其他:parallel_workers(表扫描并行度)、vacuum_truncate(是否截断表尾空页)、fillfactor 的索引版本(索引也有 fillfactor 参数)。

设置与查看:ALTER TABLE t SET (fillfactor = 80, toast_tuple_target = 64);查看 SELECT reloptions FROM pg_class WHERE relname='t';索引参数用 ALTER INDEX ... SET。作用维度:写放大与碎片(fillfactor)、IO 分布(TOAST 阈值)、清理节奏(autovacuum 族)与并行度(parallel_workers);参数是"成本-收益"调优,非默认值仅在识别到具体瓶颈时设置(如实测页分裂多、vacuum 跟不上),避免无依据乱调。注意:fillfactor 只影响后续写入的页(存量页需 VACUUM FULL);表级参数覆盖全局设置(GUC 同名参数),清除用 RESET。

答题先按三类列常用存储参数(fillfactor、toast_tuple_target、autovacuum 族与并行度)并各给含义,再讲设置(ALTER TABLE SET)、查看(reloptions)与索引参数,最后强调"按瓶颈调优、存量页需重建"的原则。

ALTER TABLE hot_table SET (fillfactor = 80, autovacuum_vacuum_scale_factor = 0.05);
ALTER TABLE big_doc SET (toast_tuple_target = 64);
SELECT reloptions FROM pg_class WHERE relname = 'hot_table';
ALTER TABLE t RESET (fillfactor);
#

82. CREATE OR REPLACE VIEW 的用途是什么?

CREATE OR REPLACE VIEW 的用途是什么?与先 DROP 再 CREATE 有何差异?

  • 视图的无中断替换
  • 依赖对象(权限、触发器、引用)的保留
  • 列结构变化的限制

用途:CREATE OR REPLACE VIEW v AS ... 在视图已存在时"直接替换其定义"(不存在则新建),用于视图定义的演进——修改查询逻辑(过滤条件、列表达式、JOIN)而无需先 DROP 再 CREATE,实现无中断更新:其一,权限保留——原视图的 GRANT(SELECT 授权)在替换后依然有效(DROP 会清掉所有授权需重建);其二,依赖保留——引用该视图的其他视图/函数/报表不用重建(对象 OID 不变,依赖关系不断);其三,并发安全——替换是原子的(一条语句),无"已删除未重建"的窗口期(先 DROP 再 CREATE 之间引用方报错)。MySQL 8.0 与 PostgreSQL 都支持(PG 中 CREATE OR REPLACE VIEW 亦保留列权限)。

限制与差异:其一,列结构——OR REPLACE 只能"保持或追加列"(新定义必须与旧定义列数兼容:可增加尾部列,不能删除/重排已有列;PG 中列顺序与数量必须一致(PG 8.x 后要求严格一致?PG 要求列集合相同或只能加列——实际 PG 允许追加列到末尾,删除/改名需 DROP+CREATE);MySQL 8.0 允许增加列、不允许删除列(报错);结构大改(删列、改列名、改类型)仍需 DROP+CREATE 或 CREATE OR REPLACE 配合显式列清单;其二,视图选项——替换会更新 WITH CHECK OPTION/安全属性等定义;其三,MySQL 中 OR REPLACE 不能改变视图的"列顺序"与删除列;其四,权限要求——需视图 owner 或 CREATE VIEW 权限(MySQL 需 CREATE VIEW + DROP 权限组合);其五,物化视图不支持 OR REPLACE(PG 需 DROP+CREATE))。

答题先讲 OR REPLACE 的核心价值(无中断替换:权限保留、依赖保留、原子无窗口期),再对比先 DROP 再 CREATE 的弊端(权限与依赖丢失、窗口期),最后列限制(列只能增不能删、物化视图不支持、权限要求)与使用建议。

-- 无中断更新视图定义(保留授权与依赖)
CREATE OR REPLACE VIEW v_orders AS
  SELECT id, amount, status FROM orders WHERE status <> 'DELETED';
-- 列结构大改仍需 DROP+CREATE
DROP VIEW v_orders;
CREATE VIEW v_orders AS SELECT id, amount FROM orders;
#

83. DROP FUNCTION 的语法与权限要求?

DROP FUNCTION 的语法与权限要求是什么?有哪些注意事项?

  • 语法(签名区分重载)
  • 权限(owner 或超级用户)
  • 依赖与 IF EXISTS/CASCADE

语法:DROP FUNCTION [IF EXISTS] 函数名(参数类型列表) [CASCADE|RESTRICT]——PostgreSQL 中必须带签名(参数类型),因为函数支持重载(同名不同参是不同函数),不带签名会报错("function name does not exist" 或要求指定参数);MySQL 语法:DROP FUNCTION [IF EXISTS] f(MySQL 函数名不能重载,无需签名);Oracle:DROP FUNCTION f;SQL Server:DROP FUNCTION dbo.f(可带架构)。权限:需函数 owner、超级用户或拥有该函数所在 schema 的相应权限(PG 中 DROP 需要函数属主或超级用户;MySQL 需要 DROP ROUTINE 权限;Oracle 需属主或 DROP ANY PROCEDURE)。

注意事项:其一,签名匹配——重载函数删除必须给出精确参数类型(DROP FUNCTION f(int) 与 DROP FUNCTION f(text) 是不同的);其二,依赖——函数被视图、触发器、其他函数、约束表达式引用时,默认 RESTRICT 报错(ERROR: cannot drop function ... because other objects depend on it),需先删依赖或用 CASCADE(级联删除引用对象,谨慎);其三,IF EXISTS——幂等(不存在时 NOTICE 不报错),适合清理脚本;其四,默认参数与省略——带默认值的函数删除时签名需与定义一致(写全或写部分?需与创建时签名匹配);其五,OR REPLACE 的关系——替换不了时删了重建;其六,事务性——PG 中 DROP FUNCTION 可回滚(DDL 事务性),MySQL 中隐式提交;其七,函数体内引用(动态 SQL 依赖)不会自动检测(PG 无法完全追踪动态依赖,CASCADE 与文档检查)。工程建议:删除前用 pg_depend 查依赖、确认无引用;脚本中加 IF EXISTS 与事务包裹(PG);生产删除函数前先 grep 应用代码与迁移脚本。

答题先给三库语法(PG 必带签名、MySQL 无需、Oracle/SQL Server),再讲权限要求(owner/超级用户或 DROP ROUTINE),最后列注意事项(重载签名、RESTRICT/CASCADE 依赖、IF EXISTS、事务性、动态依赖盲区)。

DROP FUNCTION IF EXISTS add_one(int);
DROP FUNCTION f(int) CASCADE;   -- 级联删除引用它的对象
-- 重载场景:两个同名函数按签名区分
DROP FUNCTION get_emp(int);
DROP FUNCTION get_emp(text);
#

84. PL/pgSQL 中的 %TYPE 与 %ROWTYPE 占位符的作用?

PL/pgSQL 中的 %TYPE 与 %ROWTYPE 占位符的作用是什么?如何使用?

  • %TYPE 的列类型引用
  • %ROWTYPE 的行结构引用
  • 类型同步与维护价值

作用:%TYPE 与 %ROWTYPE 是"类型引用占位符",让局部变量自动跟随表/列的类型定义,避免手工写死类型:%TYPE——引用"某列的完整类型"(含长度/精度),如 v_name users.name%TYPE(等价于 VARCHAR(50)),当列类型变更(VARCHAR(50)→VARCHAR(100))时函数变量自动跟随,无需改代码;%ROWTYPE——引用"某表的整行结构",声明一个"行记录"变量(如 r users%ROWTYPE),结构 = 表所有列,配合 SELECT * INTO r 一次取整行、按 r.column 访问,当表增删列时行变量自动适配。两者都"编译/运行时解析"(PL/pgSQL 在第一次执行时解析类型引用)。

使用场景:%TYPE——函数入参/局部变量与表列强相关的场景(如按 user_id 参数查询,参数声明为 users.id%TYPE,避免主键类型从 INT 改 BIGINT 时函数签名失配);%ROWTYPE——整行处理(SELECT INTO 行变量、FOR r IN SELECT * LOOP 行循环、触发器 NEW/OLD 是记录类型可配 %ROWTYPE 校验)、行数据的封装传递。价值:其一,类型单一来源(DRY)——表结构是类型的权威,变更只改一处;其二,避免隐式转换与溢出(参数类型与列完全一致,比较与赋值无转换);其三,可维护性——结构演进而过程代码不碎。注意事项:%TYPE 引用的是"列的当前类型"(含 NOT NULL?%TYPE 只带类型不带约束(NOT NULL 不继承));%ROWTYPE 在表结构变更(改名/删列)后引用旧列名会报错(需同步更新);%ROWTYPE 不含表的默认值(SELECT 未赋值列无值);不能对 %ROWTYPE 变量整体赋值 NULL(需逐列或 NULL::记录);触发器函数中 NEW/OLD 可用 TG_TABLE_NAME 动态获取结构(返回 record)。其他库对应:MySQL 无 %TYPE/%ROWTYPE(需手工声明)、Oracle PL/SQL 同样支持 %TYPE/%ROWTYPE(同源于此)、SQL Server 无直接对应(可用表类型)。

答题先定义两个占位符(%TYPE 引用列类型、%ROWTYPE 引用行结构)与"类型跟随表结构"的机制,再给使用场景(参数同步、整行处理)与核心价值(单一来源、避免转换、可维护),最后列注意事项(不带约束、结构变更报错、无默认值)与其他库对照。

CREATE FUNCTION get_user(uid users.id%TYPE) RETURNS users%ROWTYPE
LANGUAGE plpgsql AS $$
DECLARE
  v_name users.name%TYPE;   -- 自动跟随 name 列类型
  r users%ROWTYPE;          -- 整行结构
BEGIN
  SELECT * INTO r FROM users WHERE id = uid;
  v_name := r.name;
  RETURN r;
END $$;
#

85. 什么是聚合关系(Aggregation)?它与组合关系(Composition)有何区别?

什么是聚合关系(Aggregation)?它与组合关系(Composition)有何区别?

  • 聚合与组合的定义(整体-部分)
  • 生命周期与存在依赖的差异
  • ER 与 UML 中的表示

聚合(Aggregation)与组合(Composition)都是"整体-部分"(Whole-Part/Part-of)关系:聚合表示"整体包含部分"但部分可独立存在(弱拥有,has-a);组合表示"整体强拥有部分",部分的生命周期绑定整体(强拥有,contains,组合是聚合的强形式)。区别核心在"存在依赖与生命周期":聚合——部分可脱离整体独立存在(如"部门-员工":部门解散员工仍在;"团队-成员"),整体销毁部分不受影响(或由外部管理);组合——部分不能独立存在,整体销毁部分必销毁(如"订单-订单明细":订单删除明细随之删除;"人-心脏"),且部分通常不能共享(一个明细只属于一个订单)。UML 表示:聚合用空心菱形(整体端),组合用实心菱形;ER 模型中的弱实体+识别联系即组合语义(存在依赖),一般联系(非识别)为聚合/普通关联。

数据库建模影响:组合关系——子表外键 NOT NULL 且 ON DELETE CASCADE(父亡子亡)、常为复合主键一部分(弱实体);聚合关系——子表外键可空/独立管理(解除引用不删子行,如员工换部门只改外键),删除父对象不影响子对象。选型判断:问"删除整体时,部分是否随之消失?部分能否独立存在/被共享?"——是组合则 CASCADE 建模,是聚合则外键可空或 SET NULL。注意 UML 与 ER 的术语差异(ER 中通常不细分,弱实体≈组合);工程上组合/聚合语义要落实为"级联动作与外键可空性",否则数据模型与业务语义不符。

答题先定义聚合(弱拥有、部分可独立)与组合(强拥有、生命周期绑定),再从存在依赖、销毁级联、共享性三点对比,给 UML/ER 的符号(空心/实心菱形、弱实体)与数据库建模差异(CASCADE vs 可空外键),最后给判断问题清单。

#

86. 实体(Entity)与属性(Attribute)的根本区别是什么?请举一个反例说明。

实体(Entity)与属性(Attribute)的根本区别是什么?请举一个反例说明建模中的常见错误?

  • 实体与属性的定义区别
  • "属性还是实体"的判定准则
  • 常见建模反例与修正

根本区别:实体(Entity)是"需要独立标识与独立存在的事物/概念"(有身份、可单独查询、可被引用、有独立生命周期),属性(Attribute)是"描述实体的某个性质"(依附实体、无独立身份、单值或多值但属于实体)。判定准则:其一,"是否值得被单独查询/统计/引用"——若某信息需要作为查询条件、统计维度或外键引用(如"城市"要被按城市统计订单),它就是实体;若只是随实体展示的描述(如"备注"),是属性;其二,"是否独立变化/有生命周期"——独立维护(城市列表由行政系统管理)是实体,随实体行存亡的是属性;其三,"是否可拆分/有自身属性"——有自身属性的(订单有金额与日期)是实体。

反例:"员工表直接把"部门"存成文本列(dept VARCHAR)"——当需要按部门统计、部门改名、部门负责人、部门层级时,文本列无法保证一致性(同一部门多种写法)、无法引用部门属性、改名要全表 UPDATE;修正:部门拆为独立实体(department 表),员工表存 dept_id 外键——部门变"实体",员工.dept_id 是"属性(引用)"。另一反例:把"手机号"建模成实体(单独建 phone 表)过度设计——若手机号只是员工的一个值、无独立查询需求,应作属性(多值属性按 1NF 拆子表但仍是属性,与实体的区别在"是否有独立身份与共享引用")。核心记忆:实体 = 有身份、可被引用、独立存在;属性 = 依附描述;"是否有独立查询/引用需求"是最实用的判据。

答题先定义实体(独立标识与存在)与属性(依附描述)的根本区别,再给三条判定准则(独立查询/引用需求、生命周期、自身属性),最后用"部门文本列 vs 部门实体表"与"手机号过度建模"两个反例说明修正方法。

#

87. CREATE TABLE 的最简语法示例是什么?请声明 id、name、created_at 三列。

CREATE TABLE 的最简语法示例是什么?请用 id、name、created_at 三列给出声明?

  • CREATE TABLE 基础语法
  • 三列的类型选择
  • 默认值与主键声明

最简语法:CREATE TABLE 表名 (列定义列表);,每列"列名 类型 [约束] [默认值]"。三列示例(PostgreSQL):CREATE TABLE users (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name VARCHAR(100) NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now());(MySQL 等价:id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP;SQL Server:id BIGINT IDENTITY(1,1) PRIMARY KEY, name NVARCHAR(100) NOT NULL, created_at DATETIME2 NOT NULL DEFAULT SYSDATETIME())。要点:其一,主键——id 用自增/身份列(BIGINT 预留增长)并声明 PRIMARY KEY(自动建索引);其二,必填——name NOT NULL(业务必填字段用约束而非应用层);其三,时间戳——created_at 给 DEFAULT now()/CURRENT_TIMESTAMP(写入时自动填充,PostgreSQL 用 TIMESTAMPTZ 存 UTC、MySQL 用 DATETIME、SQL Server 用 DATETIME2);其四,类型选择——名称长度按业务(VARCHAR(100)),时间精度(TIMESTAMPTZ 微秒)。

变体:也可写 GENERATED ALWAYS AS IDENTITY(标准)而非 SERIAL;无主键也可(不推荐);加表级约束与注释(COMMENT ON)。注意:最简语法 = 列类型必须、约束可选;分号结束;标识符小写下划线(命名规范)。

答题先给最简语法骨架,再给出三列在三种方言中的完整示例(自增主键、NOT NULL、默认时间戳),最后讲解每列的类型与约束选择依据(BIGINT 预留、NOT NULL 业务必填、DEFAULT now() 自动填充)与标识符规范。

-- PostgreSQL
CREATE TABLE users (
  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- MySQL
CREATE TABLE users (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
#

88. CREATE VIEW 的基本语法示例是什么?

CREATE VIEW 的基本语法是什么?请给出示例?

  • CREATE VIEW 语法骨架
  • 列名与查询
  • 视图选项(CHECK OPTION)

基本语法:CREATE [OR REPLACE] VIEW 视图名 [(列别名列表)] AS SELECT 查询 [WITH CHECK OPTION];——列别名可选(不写则用查询输出列名),查询是任意 SELECT(单表/多表 JOIN/聚合/子查询)。示例:CREATE VIEW v_active_users AS SELECT id, name, email FROM users WHERE status = 'ACTIVE';(封装过滤逻辑);带列别名:CREATE VIEW v_sales_summary (ym, total_amount) AS SELECT date_trunc('month', order_date), SUM(amount) FROM orders GROUP BY 1;;跨表:CREATE VIEW v_order_detail AS SELECT o.id, u.name FROM orders o JOIN users u ON o.user_id = u.id;。

要点:其一,OR REPLACE——替换已有定义(保留权限与依赖);其二,WITH CHECK OPTION——可更新视图的写入校验(防止写入不可见行);其三,视图是"查询定义"不存数据(每次查询执行定义),物化需 MATERIALIZED VIEW;其四,权限——创建需 CREATE VIEW 权限(PG 中需 schema 的 CREATE + 底层表 SELECT 权限),查询视图需视图权限(不自动继承基表权限,可做权限隔离:只授视图不授表);其五,命名与 schema——视图名在 schema 内唯一(与表共享命名空间);其六,MySQL 中视图创建时检查表存在性(CREATE VIEW 时 SELECT 对象需存在),PG 允许延迟(视图创建时引用的表不存在会报错?PG 创建视图时对引用的表做依赖检查,不存在则报错(除非用占位?PG 会报错);其七,删除用 DROP VIEW。示例补充(MySQL):CREATE ALGORITHM=MERGE VIEW ...(可指定算法,8.0 默认自动))。

答题先给语法骨架(OR REPLACE/列别名/查询/CHECK OPTION),再给三个示例(过滤封装、聚合重命名列、JOIN 视图),最后列要点(权限隔离、视图不存数据、与表共享命名空间、删除 DROP VIEW)。

CREATE OR REPLACE VIEW v_active_users AS
  SELECT id, name, email FROM users WHERE status = 'ACTIVE';
-- 聚合视图 + 列别名
CREATE VIEW v_monthly_sales (month, total) AS
  SELECT date_trunc('month', order_date), SUM(amount)
  FROM orders GROUP BY date_trunc('month', order_date);
-- 可更新视图 + 写入校验
CREATE VIEW v_editable AS SELECT id, name FROM users WHERE active = true
  WITH CHECK OPTION;
#

89. 视图能否使用 ORDER BY 子句?

视图能否使用 ORDER BY 子句?有哪些限制与最佳实践?

  • 视图内 ORDER BY 的合法性
  • 标准语义(视图无序)与外层排序
  • 各库限制与提示

可以写:CREATE VIEW v AS SELECT ... ORDER BY col 在语法上合法(MySQL、PostgreSQL、SQL Server、Oracle 都允许视图定义中含 ORDER BY),但语义上"视图的排序不保证"——SQL 标准中视图是"关系/表"(无序集合),查询视图时的结果顺序只由"最外层查询的 ORDER BY"决定,视图内部的 ORDER BY 被优化器视为"仅供内部表达式使用"(如配合 LIMIT 才有意义),外层 SELECT * FROM v 通常不保留视图内排序(MySQL 8.0 前在部分场景保留、8.0 起按标准不保证;PostgreSQL 不保证)。因此视图内 ORDER BY 的合理用途只有:其一,配合 LIMIT/OFFSET 取"有序的前 N 行"(ORDER BY + LIMIT 语义绑定才有确定性);其二,配合窗口函数(ORDER BY 是窗口框架的一部分)。

限制与最佳实践:其一,视图内 ORDER BY 会增加不必要的排序开销(若外层不用顺序)——不要写;其二,需要稳定顺序时在外层查询写 ORDER BY(SELECT * FROM v ORDER BY col);其三,MySQL 8.0 起视图内 ORDER BY 被忽略(除非有 LIMIT,会报警告 "ORDER BY clause in view is ignored"?实际 8.0 中无 LIMIT 时视图 ORDER BY 被忽略并提示),SQL Server 中视图 ORDER BY 必须有 TOP/OFFSET(否则报错:"The ORDER BY clause is invalid in views... unless TOP/OFFSET");Oracle 允许但语义同标准;其四,物化视图内 ORDER BY 无意义(存储顺序由索引决定);其五,排序逻辑属于"查询层"而非"视图定义层"——视图定义查询保持"无顺序的数据集"(规范性),顺序由消费端控制。结论:视图内 ORDER BY 合法但无保证(除 LIMIT 场景),规范做法是外层排序;SQL Server 强制要求 TOP/OFFSET 才允许。

答题先确认语法合法但"视图无序"的标准语义(外层 ORDER BY 才保证),再讲 ORDER BY+LIMIT 与窗口函数两个合理用途,最后列各库限制(MySQL 8.0 忽略、SQL Server 需 TOP/OFFSET)与"排序放外层"的最佳实践。

-- 视图内 ORDER BY(仅配合 LIMIT 有意义)
CREATE VIEW v_top10 AS SELECT * FROM products ORDER BY sales DESC LIMIT 10;
-- 需要完整顺序:外层排序
SELECT * FROM v_products ORDER BY price DESC;
#

90. 什么是 IMMUTABLE、STABLE、VOLATILE 函数属性?

什么是 IMMUTABLE、STABLE、VOLATILE 函数属性?各自的作用与选择依据是什么?

  • 三档稳定性的定义
  • 对优化器的影响
  • 选择与错误标注的危害

三档属性声明"函数结果的稳定性"(PostgreSQL/Oracle;SQL Server 用 DETERMINISTIC 单档):IMMUTABLE(不可变)——相同输入永远返回相同结果,且不依赖数据库状态/时间/会话(如 ABS、UPPER、hash 函数):优化器可做常量折叠(编译期求值)、可建表达式索引、可用于物化视图与生成列、SQL 函数可内联;STABLE——在"单条 SQL 语句内"结果稳定(不随行变化),但跨语句/随时间可变化(如 now()、current_user):语句内可复用结果、可建表达式索引(PG 允许 STABLE 用于索引),不能跨语句折叠;VOLATILE(默认)——每次调用结果可能不同或函数有副作用(random()、clock_timestamp()、nextval()、含 DML 的函数):每次执行(每行求值)、不能用于表达式索引/生成列、不能折叠、可能阻止并行与部分优化、不内联。

选择依据:按函数真实行为标注——只读查询函数标 STABLE 或 IMMUTABLE(访问数据库对象或 now() 的标 STABLE、纯计算标 IMMUTABLE);有副作用或每次不同标 VOLATILE(默认,不标也行但会丧失优化)。错误标注的危害:把 VOLATILE 标成 IMMUTABLE/STABLE——表达式索引与物化视图内容错误(查询结果不一致,数据损坏级事故)、函数结果被错误缓存复用;把 IMMUTABLE 标成 VOLATILE——只是失去优化(索引不能建、折叠不做),性能损失但正确性无碍。工程规范:创建自定义函数必须显式标注稳定性;代码评审检查标注与函数体是否一致(函数体内有表访问/时间函数/随机数的不能标 IMMUTABLE);MySQL 用 DETERMINISTIC 关键字(对应 IMMUTABLE,不标则默认 NOT DETERMINISTIC,会影响复制与 binlog 记录);SQL Server 无用户可标(系统标记确定性)。

答题先定义三档(IMMUTABLE 恒定、STABLE 语句内稳定、VOLATILE 每调用可变)并各给典型函数与优化器收益/限制,再讲选择依据(真实行为匹配)与错误标注的方向性危害,最后补充 MySQL DETERMINISTIC 与规范建议。

#

91. 什么是函数重载(Function Overloading)?

什么是函数重载(Function Overloading)?数据库中的重载规则与选择机制是什么?

  • 函数重载的定义(同名不同参)
  • 数据库重载支持(PG/Oracle/MySQL 差异)
  • 调用时的解析规则

函数重载:同一命名空间下"函数名相同、参数列表(类型/数量)不同"的多个函数并存,调用时按实参类型与数量选择具体实现——把"同一逻辑的不同类型/参数形态"集中在一个名字下(如 len(str) 与 len(bytes)、date_trunc('day', ts) 与 date_trunc('hour', ts)?参数类型区分)。数据库支持:PostgreSQL 完整支持重载(函数签名 = 名称+参数类型列表,pg_proc 中同 proname 不同 proargtypes 并存,如 over(x int) 与 over(x text));Oracle 支持(PL/SQL 重载);MySQL 不支持函数重载(存储函数名必须唯一,同名报错),存储过程 8.0 中参数数量不同的同名过程?MySQL 过程也不重载;SQL Server 函数/过程不支持重载(同名不可,但参数默认值可模拟部分场景)。

解析/选择机制:调用 f(实参) 时按"参数类型匹配"选择——精确匹配优先,否则按隐式转换规则找最接近的(如 f(int) 与 f(bigint),实参 int 选 f(int);实参 smallint 可提升到 int 或 bigint,选"转换代价最小"的),多义(两个候选都同等可转)时报错("function is not unique");PG 中字符串字面量的类型推断(未知类型按上下文)与显式 CAST 可消除歧义。重载与重写的区别:重载是"同名的不同函数共存(编译/解析期选择)",重写(override)是面向对象继承中的方法替换(数据库领域少用)。工程应用:PG 内建大量重载(to_char 多种类型、date_trunc 多种粒度);自定义重载用于"同逻辑多类型参数"(如 get_stat(day date) 与 get_stat(month date));注意 DROP 时需签名(重载区分)、CALL/SELECT 时参数类型歧义要显式转换;ORM 生成 SQL 时注意重载函数的参数类型匹配。

答题先定义重载(同名不同参数类型/数量、按实参选择),再列各库支持矩阵(PG/Oracle 支持、MySQL/SQL Server 不支持),最后讲解析规则(精确匹配优先、隐式转换代价、多义报错)与工程应用、DROP 签名注意事项。

-- PostgreSQL:同名不同参数类型重载
CREATE FUNCTION fmt(x int) RETURNS text AS $$ SELECT x::text $$ LANGUAGE sql;
CREATE FUNCTION fmt(x numeric) RETURNS text AS $$ SELECT to_char(x, 'FM9990.00') $$ LANGUAGE sql;
SELECT fmt(42);        -- 选 int 版本
SELECT fmt(4.5);       -- 选 numeric 版本
-- 多义需显式转换
SELECT fmt(1.0::numeric);