多租户、反范式与文档模型

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

1. PostgreSQL 行级安全(Row-Level Security, RLS)策略的实现?

PostgreSQL 行级安全(Row-Level Security, RLS)策略如何实现?

  • RLS 概念
  • CREATE POLICY 语法
  • 启用与禁用

行级安全(RLS)允许数据库在行级限制哪些用户能访问哪行数据,无需应用层过滤。实现步骤:①在表上启用 RLS:ALTER TABLE ... ENABLE ROW LEVEL SECURITY;②创建策略:CREATE POLICY ... ON table USING (条件) WITH CHECK (条件);③条件中可用 CURRENT_USER、current_setting('app.tenant_id') 等判断当前用户。策略按操作类型(ALL/SELECT/INSERT/UPDATE/DELETE)分别定义,USING 限制现有行,WITH CHECK 限制插入/更新的新行。RLS 常用于多租户隔离。

RLS 是 PostgreSQL 实现多租户行级隔离的核心机制,策略由数据库强制,比应用层过滤更安全。FORCE ROW LEVEL SECURITY 可对表 owner 也强制。

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
  USING (tenant_id = current_setting('app.tenant_id')::int);
#
★★★

2. Schema-per-Tenant 的约束冲突,连接池、迁移成本?

Schema-per-Tenant 多租户模式有什么约束冲突?连接池、迁移成本如何?

  • Schema-per-Tenant 模式
  • 连接池问题
  • 迁移成本

Schema-per-Tenant(每租户一个 schema)模式将每个租户的数据隔离在独立 schema 中,隔离性好。但存在约束冲突:①连接池:不同租户的 schema 不同,连接复用需按租户切换 search_path,连接池管理复杂,且连接数可能随租户数增长;②迁移成本:schema 数量多时,DDL 迁移需对所有 schema 执行,迁移缓慢、易遗漏,需自动化脚本;③管理开销:schema 数量大时元数据、备份、监控成本高。适用于租户数中等、隔离要求高的场景。

Schema-per-Tenant 用 schema 隔离,隔离性和安全好,但连接池与迁移是主要痛点。SELECT search_path 或连接池按租户路由可缓解。

-- 切换租户 schema
SET search_path TO tenant_123;
-- 迁移时需遍历所有 schema
SELECT 'ALTER TABLE ' || schemaname || '.orders ADD COLUMN x INT'
FROM pg_namespace WHERE nspname LIKE 'tenant\_%';
#
★★★

3. 共享表(Shared Table)多租户的取舍,tenant_id 列、行级安全(RLS)?

共享表(Shared Table)多租户模式有什么取舍?tenant_id 列与行级安全(RLS)如何配合?

  • 共享表多租户
  • tenant_id 列
  • RLS 配合

共享表(Shared Table)多租户模式中所有租户的数据存在同一张表,通过 tenant_id 列区分租户。优点是扩展性好、成本低、迁移简单;缺点是隔离依赖应用层正确过滤 tenant_id,若漏加条件会数据泄露。为提升安全性,配合 RLS(行级安全)自动强制 tenant_id 过滤,避免应用层遗漏。通常 tenant_id 列加索引(常与主键组成复合键)。适合租户数多、数据量大的场景。

共享表是成本最低的多租户方案,RLS 是其安全性的关键补充。tenant_id 需作为查询条件并建索引。

CREATE TABLE orders (
    id BIGINT, tenant_id INT, amount NUMERIC,
    PRIMARY KEY (tenant_id, id)
);
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY p ON orders USING (tenant_id = current_setting('app.tenant_id')::int);
#
★★★

4. 多租户场景下的资源竞争,连接池、CPU、I/O 争用?

多租户场景下存在哪些资源竞争?连接池、CPU、I/O 争用如何?

  • 连接池竞争
  • CPU 争用
  • I/O 争用

多租户场景下资源竞争:①连接池:多租户共享连接池,一个租户的慢查询可能占用连接导致其他租户排队,需设置连接超时、按租户合理分配连接;②CPU:某个租户的重查询耗尽 CPU 影响其他租户,可用资源组(cgroup、PG 的 resource group)或限流控制;③I/O:大查询/大事务占用磁盘 I/O,可用 I/O 限流、缓存优化。解决方案:连接池隔离、资源配额、查询超时、监控告警。目标是在共享资源的同时保证租户间的公平性。

多租户共享资源池,需通过资源限制与隔离机制避免租户间互相影响。这是共享基础架构的常见运维挑战。

-- PostgreSQL 设置查询超时
SET statement_timeout = '30s';
-- 设置连接超时
SET idle_in_transaction_session_timeout = '60s';
#
★★★

5. 领域驱动设计(DDD)中的聚合根(Aggregate Root)、限界上下文(Bounded Context)如何映射到数据库?

领域驱动设计(DDD)中的聚合根(Aggregate Root)、限界上下文(Bounded Context)如何映射到数据库?

  • 聚合根概念
  • 限界上下文概念
  • 数据库映射

DDD 中聚合根(Aggregate Root)是聚合的入口,聚合内实体通过聚合根访问,保证一致性边界。限界上下文(Bounded Context)是领域模型的边界,不同上下文有独立模型。映射到数据库:①聚合根通常映射为一张表,聚合内的实体可映射为同表子集或独立表;②聚合的原子性通过数据库事务保证(聚合内变更应事务性);③限界上下文对应不同的数据库 schema 或独立数据库,避免跨上下文直接共享表;④聚合间通过 ID 引用而非直接关联。其核心是"聚合内强一致、聚合间最终一致"。

DDD 映射的关键是"聚合边界"与"事务边界"对齐,限界上下文用 schema/库隔离。跨上下文用事件(Event)实现最终一致。

-- 订单聚合:Order 是聚合根,OrderItem 是聚合内实体
CREATE TABLE orders (id BIGINT PRIMARY KEY, status TEXT);
CREATE TABLE order_items (
    id BIGINT PRIMARY KEY, order_id BIGINT REFERENCES orders(id),
    sku TEXT, qty INT
);
#
★★★

6. PostgreSQL RLS 的 CREATE POLICY 语法?

PostgreSQL 中 RLS 的 CREATE POLICY 语法是什么?

  • CREATE POLICY 语法
  • USING 与 WITH CHECK
  • 命令类型

CREATE POLICY 语法:CREATE POLICY name ON table [FOR command] TO role USING (using_expr) WITH CHECK (check_expr);。command 可选 ALL/SELECT/INSERT/UPDATE/DELETE,roles 指定适用角色,USING 决定现有行是否可见(SELECT/UPDATE/DELETE),WITH CHECK 决定插入/更新的新行是否合法(INSERT/UPDATE)。可通过 ALTER POLICY 修改,DROP POLICY 删除。需先启用 RLS(ENABLE ROW LEVEL SECURITY)。

USING 与 WITH CHECK 的区别:USING 过滤可见行,WITH CHECK 校验新写入的行。这是 RLS 策略设计的核心。

CREATE POLICY tenant_sel ON orders
  FOR SELECT USING (tenant_id = current_setting('app.tenant_id')::int);
CREATE POLICY tenant_ins ON orders
  FOR INSERT WITH CHECK (tenant_id = current_setting('app.tenant_id')::int);
#
★★★

7. RLS 的 SELECT、INSERT、UPDATE、DELETE 策略?

RLS 的 SELECT、INSERT、UPDATE、DELETE 策略如何定义?

  • 各操作类型策略
  • USING 与 WITH CHECK
  • 组合策略

RLS 可为不同操作定义策略:SELECT 用 USING 过滤可见行;INSERT 用 WITH CHECK 校验新行;UPDATE 同时用 USING(过滤可更新的现有行)和 WITH CHECK(校验更新后的行);DELETE 用 USING 过滤可删除的行。若定义 FOR ALL 策略则覆盖所有操作。可分别为不同角色定义策略,多个策略取并集(OR)。UPDATE 需注意:使用 USING 的行可能被更新,但更新结果必须满足 WITH CHECK。

各操作类型策略的 USING/WITH CHECK 组合是 RLS 设计的核心。常见做法是定义 FOR ALL 简化,或按操作细分。

CREATE POLICY p_sel ON orders FOR SELECT USING (tenant_id = current_setting('app.tenant_id')::int);
CREATE POLICY p_ins ON orders FOR INSERT WITH CHECK (tenant_id = current_setting('app.tenant_id')::int);
CREATE POLICY p_upd ON orders FOR UPDATE
  USING (tenant_id = current_setting('app.tenant_id')::int)
  WITH CHECK (tenant_id = current_setting('app.tenant_id')::int);
CREATE POLICY p_del ON orders FOR DELETE USING (tenant_id = current_setting('app.tenant_id')::int);
#
★★★

8. UUID 主键的分布式友好性与索引体积代价?

UUID 主键的分布式友好性与索引体积代价是什么?

  • UUID 分布式友好性
  • 随机性
  • 索引体积与性能

UUID 主键的优点:①分布式友好:客户端可离线生成,无需数据库自增,天然适合分布式与分库分表;②全局唯一:避免跨库冲突。代价:①索引体积大:UUID 占 16 字节(或 36 字符文本),比 INT(4 字节)/BIGINT(8 字节)大,主键索引与二级索引都占用更大空间;②随机性:UUID v4 随机,插入顺序随机导致 B-tree 页分裂频繁、索引碎片化、写入性能下降;③不适合聚簇索引。缓解:用 UUID v7(时间有序)或雪花 ID 改善顺序性。

UUID 权衡分布式便利与存储/性能。UUID v7 带时间戳,兼具唯一性与顺序性,是分布式友好的改进方案。

-- UUID 主键
CREATE TABLE t (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), ...);
-- 相比 INT 主键空间更大
CREATE TABLE t (id INT PRIMARY KEY AUTO_INCREMENT, ...);
#
★★★

9. 主键的数据类型选择,INT vs BIGINT vs UUID vs 字符串?

主键的数据类型如何选择?INT vs BIGINT vs UUID vs 字符串?

  • 各类型特点
  • 范围与性能
  • 应用场景

主键类型选择:①INT:4 字节,范围约 21 亿,适合数据量小、单机场景,性能好、索引小;②BIGINT:8 字节,范围大,适合大数据量、高并发写入,需自增或雪花 ID;③UUID:16 字节,分布式友好、可离线生成,但体积大、随机性影响索引;④字符串(如自然键):可读性好,但体积大、性能差,一般不用做主键,除非是特定自然键。选择依据:数据量、是否需要分布式、插入性能、可读性。OLTP 常用 BIGINT 自增或雪花 ID,分布式常用 UUID v7。

主键类型直接影响索引体积与写入性能。INT/BIGINT 性能好,UUID 分布式友好,字符串最不推荐。

CREATE TABLE t (id BIGINT PRIMARY KEY, ...);   -- 大数据量
CREATE TABLE u (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), ...); -- 分布式
#
★★★

10. 主键的物理顺序与聚簇索引的关系(InnoDB IOT)?

主键的物理顺序与聚簇索引的关系(InnoDB IOT)是什么?

  • 聚簇索引
  • IOT
  • 主键顺序

InnoDB 是索引组织表(IOT,Index-Organized Table),数据按主键(聚簇索引)的物理顺序存储。主键决定了行的物理排列:主键顺序、插入顺序与物理存储一致。因此:①单调递增的主键(自增、雪花 ID)导致顺序插入,减少页分裂、写入高效;②随机主键(UUID v4)导致随机插入,频繁页分裂、索引碎片化、写入性能下降。二级索引(非聚簇索引)叶子节点存主键值,回表需查主键。理解主键物理顺序对设计高性能主键至关重要。

InnoDB 表中主键即聚簇索引,决定着行的物理位置。主键顺序与插入顺序越一致,写入性能越好。

-- 自增主键:顺序插入,减少页分裂
CREATE TABLE t (id BIGINT AUTO_INCREMENT PRIMARY KEY, ...);
-- UUID 主键:随机插入,易页分裂
#
★★★

11. 唯一键(UNIQUE)与主键的 NULL 处理差异?

唯一键(UNIQUE)与主键的 NULL 处理差异是什么?

  • 主键非空
  • 唯一键允许 NULL
  • 多 NULL 行为(PG vs MySQL)

主键(PRIMARY KEY)隐含 NOT NULL 约束,不允许 NULL,且每行唯一。唯一键(UNIQUE)允许 NULL,且标准 SQL 中 NULL 互不相等,因此唯一键可存在多个 NULL。PostgreSQL 遵循这一约定(多个 NULL 允许)。MySQL InnoDB 也允许唯一索引多个 NULL;但 SQL Server 的 UNIQUE 索引对 NULL 也强制唯一(只允许一个 NULL,除非加过滤)。这是唯一键与主键在 NULL 处理上的关键差异。

主键非空 + 唯一,唯一键允许 NULL 且(PG/MySQL)多 NULL 共存。SQL Server 行为不同,需注意数据库差异。

-- PostgreSQL:唯一键允许多个 NULL
CREATE TABLE t (id INT PRIMARY KEY, email TEXT UNIQUE);
INSERT INTO t VALUES (1, NULL), (2, NULL);  -- PostgreSQL 允许
#
★★★

12. MySQL InnoDB 主键的聚簇影响?

MySQL InnoDB 主键的聚簇影响是什么?

  • 聚簇索引
  • 主键选择
  • 二级索引与回表

InnoDB 主键是聚簇索引,数据行按主键顺序物理存储。影响:①主键决定了数据物理布局,选择一个好的主键(单调递增)能提升写入性能、减少页分裂;②二级索引(非聚簇索引)的叶子节点存储主键值,查询二级索引后需回表(通过主键查数据行),因此主键应尽量小(小主键减少二级索引体积);③若无显式主键,InnoDB 会选第一个非空唯一索引或隐式生成 rowid 作为聚簇键。主键设计直接影响存储与性能。

InnoDB 聚簇索引是理解其存储架构的核心。主键大小影响所有二级索引,主键顺序影响写入。

-- 推荐单调递增主键
CREATE TABLE t (id BIGINT AUTO_INCREMENT PRIMARY KEY, ...);
-- 小主键减少二级索引体积
#
★★★

13. UUID v4 与 v7 的索引写入性能?

UUID v4 与 v7 的索引写入性能差异是什么?

  • UUID v4 随机性
  • UUID v7 时间有序
  • 索引写入

UUID v4 是完全随机的,插入顺序随机,导致 B-tree 页频繁分裂、索引碎片化、缓存命中率低,写入性能差。UUID v7 以时间戳为前缀,生成的 ID 大致单调递增,插入顺序接近顺序,减少页分裂、提升缓存命中,写入性能显著优于 v4,同时保持分布式唯一性。因此高并发写入场景推荐 UUID v7 而非 v4。v7 兼容 v4 的分布式优点,且改善顺序性。

UUID 版本差异主要在顺序性。v7 时间有序,兼顾唯一性与索引写入性能,是数据库主键的推荐选择。

-- PostgreSQL 生成 UUID v7(需扩展)
SELECT uuidv7();  -- 顺序近似递增
-- UUID v4 随机
SELECT gen_random_uuid();
#
★★★

14. 主键的 ALTER TABLE ADD PRIMARY KEY 的代价?

主键的 ALTER TABLE ADD PRIMARY KEY 有什么代价?

  • 重建表
  • 索引构建
  • 锁与停机

ALTER TABLE ADD PRIMARY KEY 会为表创建聚簇索引(InnoDB)或唯一索引(PG),代价:①InnoDB 中若表无主键,加主键会重建整个表(数据按新主键重新组织),耗时且占用大量临时空间;②PG 中加主键会扫描全表构建唯一索引,建索引期间加锁,可能阻塞写入;③大表操作耗时,需选低峰期或 Online Schema Change 工具。因此建表时就应设计好主键,避免后期添加。

后期加主键成本高,尤其 InnoDB 可能重建表。设计阶段确定主键是数据库最佳实践。

-- 后期加主键(大表成本高)
ALTER TABLE t ADD PRIMARY KEY (id);
-- 最好在 CREATE TABLE 时指定
#
★★★

15. 唯一约束的 NULL 处理(PG 多 NULL vs SQL Server 单 NULL)?

唯一约束的 NULL 处理在不同数据库中的差异是什么?

  • PG 多 NULL
  • SQL Server 单 NULL
  • NULL 语义

唯一约束对 NULL 的处理因数据库而异:PostgreSQL 遵循标准 SQL,NULL 互不相等,因此唯一约束允许多个 NULL 行共存。SQL Server 的传统唯一索引把 NULL 视为值,强制唯一,只允许一个 NULL(除非用过滤索引 UNIQUE ... WHERE col IS NOT NULL)。MySQL 与 PG 类似,允许多个 NULL。这一差异影响业务设计:若需区分"未设置"与"已设置",PG 更灵活;SQL Server 需用过滤索引规避。

唯一的 NULL 处理是跨数据库兼容的重要细节。设计时需考虑目标数据库的 NULL 语义。

-- PostgreSQL:唯一键允许多个 NULL
CREATE TABLE t (id INT PRIMARY KEY, email TEXT UNIQUE);
-- SQL Server:唯一索引默认只允许一个 NULL,用过滤索引规避
CREATE UNIQUE INDEX u ON t(email) WHERE email IS NOT NULL;
#
★★★

16. 反范式的优势,减少 JOIN、提升查询性能?

反范式(Denormalization)的优势是什么?如何减少 JOIN、提升查询性能?

  • 反范式概念
  • 减少 JOIN
  • 查询性能

反范式的优势:①减少 JOIN:通过冗余字段或预聚合,把多表关联的数据合并到一张表,查询无需 JOIN,直接取数,降低查询复杂度与延迟;②提升查询性能:减少关联计算与 IO,尤其适合读多写少的查询场景;③简化应用层:业务侧查询更简单。代价是数据冗余、更新需同步、一致性维护成本。典型应用:冗余外键列、存派生数据、预计算聚合。

反范式以一致性与空间换查询性能。需权衡写入频率与查询模式,选择性地冗余热点数据。

-- 规范化:JOIN 查询
SELECT o.id, c.name FROM orders o JOIN customers c ON o.customer_id = c.id;
-- 反范式:orders 冗余 customer_name,无需 JOIN
SELECT id, customer_name FROM orders;
#
★★★

17. 反范式(Denormalization)的常见模式,冗余字段、派生列、预计算聚合?

反范式(Denormalization)的常见模式有哪些?冗余字段、派生列、预计算聚合是什么?

  • 冗余字段
  • 派生列
  • 预计算聚合

反范式常见模式:①冗余字段:把关联表字段复制到主表(如订单冗余客户名称),减少 JOIN,需在更新时同步;②派生列(Derived Column):存储可由其他字段计算的值(如订单总金额、年龄),可加速查询,需保证与源数据一致;③预计算聚合(Precomputed Aggregate):把聚合结果(如订单数、销售总额)预先算好存储(汇总表/物化视图),避免每次实时聚合。这些模式以空间与一致性成本换取查询性能,常通过触发器、批处理或物化视图维护一致性。

反范式三模式是 OLAP 与高并发读场景的常用手段。预计算聚合常用物化视图或汇总表自动维护。

-- 预计算聚合:物化视图
CREATE MATERIALIZED VIEW order_stats AS
SELECT customer_id, COUNT(*), SUM(amount) FROM orders GROUP BY customer_id;
-- 派生列
CREATE TABLE orders (id INT, qty INT, price NUMERIC, total NUMERIC);
#
★★★

18. 预计算聚合表(Aggregate Table),物化视图、汇总表?

预计算聚合表(Aggregate Table)如何实现?物化视图、汇总表有什么区别?

  • 预计算聚合
  • 物化视图
  • 汇总表

预计算聚合表把聚合结果预先存储,避免查询时实时聚合大表。实现方式:①物化视图(Materialized View):数据库把 SELECT 结果存储为表,PostgreSQL 用 REFRESH MATERIALIZED VIEW 刷新,MySQL 无原生物化视图(用定时汇总表或触发刷新);②汇总表(Aggregate/Summary Table):手工维护的聚合表,通过批处理、触发器或 ETL 定期更新。物化视图由数据库管理,汇总表由应用管理。适用于明细大、查询频繁、可容忍延迟聚合的场景。

预计算聚合用空间换时间,常用于数据仓库与报表。物化视图更省心,汇总表更灵活。

-- PostgreSQL 物化视图
CREATE MATERIALIZED VIEW sales_summary AS
SELECT region, SUM(amount) FROM sales GROUP BY region;
REFRESH MATERIALIZED VIEW sales_summary;
-- 汇总表:批处理更新
#
★★★

19. 星型模型(Star Schema)的反范式?

星型模型(Star Schema)如何体现反范式?

  • 星型模型结构
  • 维度表反范式
  • 事实表

星型模型由中央事实表(Fact Table)与四周的维度表(Dimension Table)组成,是数据仓库的常见设计。它体现反范式:维度表不遵守 3NF,而是主动冗余、扁平化(如客户维度表把城市、省份、国家都冗余在一张表,形成"雪花"被折叠成星型),以减少 JOIN 层级、提升查询性能。事实表存度量与维度外键,维度表存描述性属性。星型模型查询简单、聚合高效,是 OLAP 的核心反范式设计。

星型模型通过"维度表扁平化"减少 JOIN 深度,属于典型反范式。比雪花模型更简单、查询更快。

-- 维度表维度扁平化(冗余城市/省份/国家)
CREATE TABLE dim_customer (
    id INT PRIMARY KEY, name TEXT,
    city TEXT, province TEXT, country TEXT
);
-- 事实表
CREATE TABLE fact_sales (
    id BIGINT PRIMARY KEY, customer_id INT, amount NUMERIC,
    FOREIGN KEY (customer_id) REFERENCES dim_customer(id)
);
#
★★★

20. 列数过多(PostgreSQL 1600 列限制)的存储开销,NULL bitmap?

PostgreSQL 1600 列限制与列数过多的存储开销是什么?NULL bitmap 如何影响?

  • 列数限制
  • NULL bitmap
  • 存储开销

PostgreSQL 单表最多 1600 列(实际受页大小限制)。列数过多会带来存储开销:每行有固定的行头(HEAP tuple header),且 PG 用 NULL bitmap 记录哪些列为 NULL,NULL bitmap 的大小取决于列数(每 8 列 1 字节,最多 8 字节)。列数多时即便全为 NULL,行也会变大,增加 IO 与存储。此外列数多会导致表结构复杂、维护困难。设计时应避免过度宽表,常用 JSONB、稀疏列或子表拆分。

NULL bitmap 让 NULL 列也有成本。列数过多增加行宽与 NULL bitmap 开销,是"列数过多"的主要考量。

-- 列数过多场景:考虑用 JSONB 整合
CREATE TABLE t (id INT PRIMARY KEY, attrs JSONB);
-- 或子表拆分
#
★★★

21. 大宽表的查询优化,列裁剪(Column Pruning)、分区?

大宽表的查询优化有哪些手段?列裁剪(Column Pruning)、分区如何应用?

  • 列裁剪
  • 分区
  • 大宽表优化

大宽表查询优化:①列裁剪(Column Pruning):查询只 SELECT 需要的列,避免读取整行,减少 IO;列存引擎自动按列裁剪,行存需显式少选列;②分区:按分区键(如时间)把大表拆成多个分区,查询时分区裁剪只扫相关分区,加快查询;③索引:对高频查询列建索引;④宽表拆分:把不常用列拆到子表或 JSONB。优化目标是减少扫描数据量。

大宽表最大问题是 IO 大,列裁剪与分区是核心优化手段。列存引擎对宽表尤为有利。

-- 列裁剪:只查需要的列
SELECT id, name FROM wide_table WHERE id = 1;
-- 分区:按时间分区
CREATE TABLE t (...) PARTITION BY RANGE (created_at);
#
★★★

22. 稀疏列的存储优化,稀疏列存储(sparse column)、EAV 模型?

稀疏列的存储优化有哪些?稀疏列存储(sparse column)、EAV 模型是什么?

  • 稀疏列
  • 稀疏列存储
  • EAV 模型

稀疏列指表中大量行为 NULL 的列。存储优化方案:①稀疏列存储(SQL Server 的 sparse column):对稀疏列使用特殊优化,NULL 值几乎不占存储,需设置 SPARSE 属性;②EAV 模型:实体-属性-值表,把属性作为行存储,只存有值的属性,避免大量 NULL,但查询复杂、类型不统一;③JSONB:半结构化存储,把稀疏属性放 JSON 中,灵活且可索引;④列存:列存引擎对 NULL 压缩效果好。选择取决于稀疏程度与查询模式。

稀疏列优化在"数据密度"上做文章。EAV 与 JSONB 都避免 NULL 列,JSONB 更现代、查询更强。

-- SQL Server 稀疏列
CREATE TABLE t (id INT PRIMARY KEY, optional_col INT SPARSE NULL);
-- EAV 模型
CREATE TABLE eav (entity_id INT, attr TEXT, value TEXT, PRIMARY KEY(entity_id, attr));
#
★★★

23. JSONB 与稀疏列的取舍?

JSONB 与稀疏列的取舍是什么?

  • JSONB 特点
  • 稀疏列特点
  • 取舍

JSONB 与稀疏列都是处理"可选属性"的手段。JSONB:把稀疏属性放 JSON 对象中,灵活、动态 schema、可 GIN 索引、支持半结构化查询,但失去关系约束、类型检查弱、查询语法复杂。稀疏列/宽表:每个属性独立列,有类型约束、可索引、查询直观,但列多时 NULL 多、加列成本高。取舍:属性数量少且固定、需强约束用稀疏列;属性多且动态、结构多变用 JSONB。现代数据库常两者结合。

JSONB 适合"属性多、动态"场景,稀疏列适合"属性少、固定、需约束"场景。二者可互补。

-- JSONB 存储动态属性
CREATE TABLE products (id INT PRIMARY KEY, attrs JSONB);
-- 稀疏列存储固定可选属性
CREATE TABLE products (id INT PRIMARY KEY, color TEXT NULL, size TEXT NULL);
#
★★★

24. 稀疏列的 GIN 索引,大量 NULL/空值列如何用 GIN 索引加速查询,与 B-tree 在稀疏数据上的差异与代价?

稀疏列的 GIN 索引如何加速查询?与 B-tree 在稀疏数据上的差异与代价是什么?

  • GIN 索引原理
  • 稀疏数据上的应用
  • B-tree 差异

稀疏列(或 JSONB)中大量 NULL/空值,B-tree 索引通常不索引 NULL,且对多值/稀疏数据效果差。GIN(Generalized Inverted Index)索引适合多值类型与稀疏数据:对 JSONB 或数组,GIN 为每个键建立索引项,查询时通过倒排条目快速定位,支持 @>、? 等操作符。在稀疏数据上,GIN 只索引有值的键,避免 NULL 的浪费,查询加速明显。代价:GIN 写入开销大(每个键单独索引项)、占空间、更新慢。B-tree 适合等值/范围、稠密数据;GIN 适合多值/稀疏/全文。

GIN 与 B-tree 的核心差异:B-tree 按值排序适合等值/范围,GIN 是倒排结构适合多值/稀疏。稀疏数据用 GIN 更高效。

-- JSONB 稀疏列用 GIN 索引
CREATE INDEX idx_attrs ON products USING gin (attrs);
-- 查询
SELECT * FROM products WHERE attrs @> '{"color": "red"}';
#
★★★

25. PostgreSQL JSONB 与 MongoDB 的对比,查询语言、事务支持、索引?

PostgreSQL JSONB 与 MongoDB 的对比?查询语言、事务支持、索引?

  • 查询语言
  • 事务支持
  • 索引

PostgreSQL JSONB 与 MongoDB 都是文档数据存储,但差异:①查询语言:PG 用 SQL + JSONB 操作符(@>、->、jsonb_path_query),MongoDB 用原生文档查询(MQL)与聚合管道;②事务支持:PG 支持 ACID 事务(含 JSONB 列),MongoDB 4.0+ 支持多文档事务,但语义与成熟度不如 PG;③索引:PG JSONB 用 GIN 索引,MongoDB 用 B-tree/哈希等索引,支持地理空间与 TTL;④模型:PG 是关系+文档混合,MongoDB 是纯文档。选择取决于团队 SQL 熟悉度、事务需求与查询模式。

PG JSONB 兼具关系与文档能力,MongoDB 更原生文档。PG 事务与 SQL 更强,MongoDB 灵活性与水平扩展更强。

-- PostgreSQL JSONB 查询
SELECT * FROM products WHERE attrs @> '{"color": "red"}';
-- MongoDB 等价
-- db.products.find({ attrs: { color: "red" } })
#
★★★

26. 关系数据库模拟文档数据库,JSONB 列、hstore?

关系数据库如何模拟文档数据库?JSONB 列、hstore 是什么?

  • JSONB 列
  • hstore
  • 模拟文档模型

关系数据库可用 JSONB/hstore 列模拟文档数据库:把半结构化数据存成一个 JSON/键值列,保留灵活 schema。PostgreSQL 的 JSONB 是二进制 JSON,支持索引(GIN)、查询操作符(@>、?)与路径查询;hstore 是键值对类型,只有字符串键值,功能弱于 JSONB,适合简单键值。这种模拟让关系库兼具文档灵活性,但约束、类型检查弱于纯关系。适合"关系为主、少量动态属性"的场景。

JSONB 是 PG 模拟文档模型的主流,hstore 是历史方案。JSONB 支持嵌套与索引,功能更强。

CREATE TABLE t (id INT PRIMARY KEY, data JSONB);
-- hstore
CREATE TABLE t2 (id INT PRIMARY KEY, kv hstore);
CREATE EXTENSION IF NOT EXISTS hstore;
#
★★★

27. 关系模型与文档模型的对比,结构化 vs 半结构化、JOIN 能力?

关系模型与文档模型的对比?结构化 vs 半结构化、JOIN 能力?

  • 结构化 vs 半结构化
  • JOIN 能力
  • schema 灵活性

关系模型:结构化强,schema 固定,属性有类型约束,通过 JOIN 关联表,事务与完整性强,适合规范化、强一致、复杂查询场景。文档模型:半结构化,schema 灵活,以文档为单位存储(可嵌套),无 JOIN 或较少 JOIN(文档内嵌),适合多变 schema、嵌套结构、快速迭代场景。关系模型 JOIN 能力强但拆分细,文档模型查询快但跨文档关联弱。选择取决于数据关系复杂度、schema 稳定性与查询模式。

关系模型强在约束与 JOIN,文档模型强在灵活与嵌套。现代系统常混合使用。

-- 关系:JOIN 关联
SELECT o.id, c.name FROM orders o JOIN customers c ON o.customer_id = c.id;
-- 文档:订单内嵌客户信息,无需 JOIN
-- { "order": {"id": 1, "customer": {"name": "Alice"}}}
#
★★★

28. 文档模型的反范式与查询性能,嵌套 vs 引用?

文档模型的反范式与查询性能如何?嵌套 vs 引用?

  • 嵌套 vs 引用
  • 反范式
  • 查询性能

文档模型中数据可嵌套(embedded)或引用(reference)。嵌套:把相关数据内嵌到文档中,读时一次取回、无需关联查询,性能好,但存在数据重复、更新需多处同步(反范式)。引用:用 ID 引用其他文档,数据唯一、无重复,但查询需多次查找或关联(性能差)。取舍:数据常一起读、关系紧密用嵌套;数据独立、更新频繁、被多处引用用引用。嵌套是文档模型的反范式手段。

文档模型的"嵌套 vs 引用"类似关系模型的"反范式 vs 规范化"。需根据读写模式选择。

// 嵌套:订单内嵌客户
{ order: { id: 1, customer: { name: "Alice" } } }
// 引用:订单引用客户 ID
{ order: { id: 1, customer_id: 100 } }
#
★★★

29. 聚合根(Aggregate Root)的文档建模?

聚合根(Aggregate Root)在文档建模中如何应用?

  • 聚合根概念
  • 文档建模
  • 聚合一致性

DDD 中聚合根是聚合的入口,聚合内实体通过聚合根访问,保证一致性边界。在文档数据库建模中,聚合根通常对应一个文档:把聚合内的实体嵌套进聚合根文档中,一次读取整个聚合,天然满足聚合的事务一致性(文档级原子操作)。跨聚合的关联用引用(ID)而非嵌套。这种建模让"聚合 = 文档",配合文档原子性,实现聚合内强一致。

文档数据库的"文档原子性"与 DDD 的"聚合一致性"天然契合,聚合根映射为文档是常见的 NoSQL 建模实践。

// 订单聚合根文档,内含订单项
{ orderId: 1, status: "NEW",
  items: [ { sku: "A", qty: 2 }, { sku: "B", qty: 1 } ] }
#
★★★

30. JSONB 的查询能力,@>、?|、jsonb_path_query 等操作符如何支撑半结构化查询,与关系列的查询能力边界在哪里?

JSONB 的查询能力如何?@>、?|、jsonb_path_query 等操作符如何支撑半结构化查询?与关系列的查询能力边界在哪里?

  • JSONB 操作符
  • 半结构化查询
  • 与关系列边界

PostgreSQL JSONB 提供丰富操作符:@>(包含)、<@(被包含)、?(键存在)、?|(任一键存在)、?&(全部键存在)、-> 和 ->>(取键值)、jsonb_path_query(SQL/JSON 路径查询)。这些操作符结合 GIN 索引支撑半结构化查询。查询能力边界:JSONB 支持键值、数组、嵌套查询,但相比关系列:类型检查弱(无强类型约束)、无外键、统计信息弱、部分查询(如范围需 cast)语法复杂。适合"属性动态、查询以键值为主"的场景;关系列适合强类型、范围、聚合查询。

JSONB 查询能力强但弱于关系列的强类型与优化。边界在于"schema 是否固定、查询是否需强类型运算"。

-- 包含查询
SELECT * FROM products WHERE attrs @> '{"color": "red"}';
-- 键存在
SELECT * FROM products WHERE attrs ? 'color';
-- 路径查询
SELECT * FROM products WHERE jsonb_path_query(attrs, '$.color') = '"red"';
#
★★★

31. MongoDB 与 PostgreSQL 的事务对比?

MongoDB 与 PostgreSQL 的事务对比是什么?

  • 事务支持
  • ACID
  • 隔离级别

PostgreSQL 支持完整 ACID 事务,具备多隔离级别(读已提交、可重复读、串行化)、MVCC、复杂事务与约束,事务成熟度与可靠性高。MongoDB 4.0+ 支持多文档事务(副本集)、4.2+ 支持分片集群事务,但采用 WiredTiger 快照隔离,事务语义与隔离级别较少、历史短、性能与兼容性有局限。对强事务一致性要求(金融、订单)PG 更可靠;对高吞吐、灵活模型 MongoDB 更合适。需评估业务的事务强度。

PG 事务成熟、隔离级别丰富,MongoDB 事务较新、语义简单。事务需求是选型关键。

-- PG 事务
BEGIN;
UPDATE orders SET status='PAID' WHERE id=1;
UPDATE inventory SET qty=qty-1 WHERE sku='A';
COMMIT;
#
★★★

32. MongoDB 的聚合管道与 SQL 的对比?

MongoDB 的聚合管道与 SQL 的对比是什么?

  • 聚合管道
  • SQL 对应
  • 差异

MongoDB 聚合管道(Aggregation Pipeline)用阶段($match、$group、$project、$sort、$limit、$lookup 等)处理文档,与 SQL 的 WHERE、GROUP BY、SELECT、ORDER BY、LIMIT、JOIN 对应。$lookup 相当于 LEFT JOIN,$unwind 相当于展开数组。差异:聚合管道是链式 JSON 管道,灵活但语法复杂;SQL 是声明式且优化器成熟。对复杂聚合,SQL 更易读、优化更好;MongoDB 管道适合文档操作与灵活变换。

聚合管道与 SQL 功能上有对应关系,但侧重点不同。映射关系是跨模型理解的基础。

// MongoDB 聚合管道:按类目分组求和
db.orders.aggregate([
  { $match: { status: "paid" } },
  { $group: { _id: "$category", total: { $sum: "$amount" } } },
  { $sort: { total: -1 } }
])
// 等价 SQL
// SELECT category, SUM(amount) FROM orders WHERE status='paid' GROUP BY category ORDER BY total DESC
#
★★

33. 关系数据库的 JSON 列索引?

关系数据库的 JSON 列如何建索引?

  • JSON 列索引
  • 表达式索引
  • GIN 索引

关系数据库为 JSON 列建索引:①PostgreSQL:对 JSONB 列用 GIN 索引(支持 @>、? 等操作符),或对 JSON 字段用表达式索引(如 (data->>'key'));②MySQL:对 JSON 列用函数索引(如 (CAST(data->>'$.key' AS ...)))或虚拟列 + 索引;③SQL Server:对 JSON 字段用计算列 + 索引。JSON 列索引能加速键值查询,但需注意表达式要与查询一致,且 JSON 索引无法像普通列那样覆盖所有查询。

JSON 列索引需针对特定键或操作符建索引(GIN/表达式/虚拟列),无法像普通列全覆盖。

-- PostgreSQL JSONB GIN 索引
CREATE INDEX idx ON t USING gin (data);
-- PostgreSQL 表达式索引
CREATE INDEX idx2 ON t ((data->>'name'));
-- MySQL 虚拟列索引
ALTER TABLE t ADD COLUMN name VARCHAR(50) AS (data->>'$.name') STORED;
CREATE INDEX idx ON t (name);
#
★★

34. Database-per-Tenant 的隔离性、性能、运维成本?

Database-per-Tenant 多租户模式的隔离性、性能、运维成本如何?

  • 隔离性
  • 性能
  • 运维成本

Database-per-Tenant(每租户一个数据库)模式为每个租户创建独立数据库。隔离性最好:数据、schema、资源完全隔离,一个租户的问题不影响其他租户。性能:无租户间干扰、可独立优化,但租户多时数据库数多,连接、资源管理复杂。运维成本最高:需管理大量数据库、备份、迁移、监控,schema 变更需逐个执行,成本高昂。适合租户数少、隔离要求极高(金融、合规)的场景。

Database-per-Tenant 隔离最强但成本最高,适用于租户数少、隔离要求严格的企业级场景。

-- 每租户一个数据库
CREATE DATABASE tenant_123;
CREATE DATABASE tenant_456;
-- 迁移需遍历所有数据库
#
★★

35. RLS 的绕过风险,超级用户、表 owner、FORCE ROW LEVEL SECURITY?

RLS 的绕过风险是什么?超级用户、表 owner、FORCE ROW LEVEL SECURITY 如何影响?

  • 超级用户绕过
  • 表 owner 绕过
  • FORCE ROW LEVEL SECURITY

RLS 的绕过风险:①超级用户(superuser)始终绕过 RLS,FORCE 对其不生效;②表 owner 默认绕过 RLS(拥有表权限的角色),可误读/篡改数据;③BYPASSRLS 属性角色可直接绕过 RLS。为增强安全性:ALTER TABLE ... FORCE ROW LEVEL SECURITY 使表 owner 也受 RLS 约束;避免授予 BYPASSRLS 给普通角色。超级用户始终可绕过(需通过其他权限控制)。理解这些绕过渠道是正确部署 RLS 的关键。

RLS 不是绝对安全,超级用户、owner、BYPASSRLS 都会绕过。FORCE ROW LEVEL SECURITY 可约束 owner。

-- 强制表 owner 也受 RLS 约束
ALTER TABLE orders FORCE ROW LEVEL SECURITY;
-- 回收 BYPASSRLS
ALTER ROLE app_user NOBYPASSRLS;
#
★★

36. 多租户数据隔离的三种模式,单数据库单 schema、单数据库多 schema、多数据库?

多租户数据隔离的三种模式是什么?单数据库单 schema、单数据库多 schema、多数据库?

  • 三种模式
  • 隔离性
  • 取舍

多租户数据隔离三种模式:①单数据库单 schema(共享表):所有租户同一 schema,用 tenant_id 区分,成本最低、扩展性最好,但隔离靠应用层 + RLS;②单数据库多 schema(Schema-per-Tenant):每租户一个 schema,隔离较好,成本中等,但连接池、迁移复杂;③多数据库(Database-per-Tenant):每租户一个数据库,隔离最强、成本最高。选择依据:租户数量、隔离要求、成本预算。共享表最常用,隔离要求高时用多 schema 或多库。

三种模式在"隔离性、成本、扩展性"间权衡。从共享表到多库,隔离增强、成本上升。

-- 共享表:tenant_id 区分
CREATE TABLE orders (tenant_id INT, ...);
-- 多 schema:每租户一个 schema
CREATE SCHEMA tenant_123;
-- 多库:每租户一个库
CREATE DATABASE tenant_123;
#
★★

37. 多租户数据迁移的挑战,单 schema 迁移 vs 多 schema 迁移?

多租户数据迁移的挑战是什么?单 schema 迁移 vs 多 schema 迁移?

  • 单 schema 迁移
  • 多 schema 迁移
  • 迁移策略

多租户数据迁移挑战:①单 schema(共享表)迁移:只需迁移一张表,DDL 一次执行,简单、成本低,但影响所有租户,需谨慎评估停机窗口;②多 schema(Schema-per-Tenant)迁移:需对每个租户 schema 执行 DDL,schema 数量多时迁移慢、易遗漏,需自动化脚本遍历所有 schema,且需保证迁移一致性。多库迁移同理需遍历所有数据库。挑战在于"规模"与"一致性",需自动化与分批迁移。

多 schema/多库迁移的主要挑战是"数量多 + 一致性",需自动化脚本与分批执行。单 schema 迁移简单但影响面大。

-- 遍历所有租户 schema 执行迁移
SELECT format('ALTER TABLE %I.%I ADD COLUMN x INT',
  schemaname, 'orders')
FROM pg_namespace WHERE nspname LIKE 'tenant\_%';
#
★★

38. RLS 的会话设置(SET app.tenant_id)?

RLS 的会话设置(SET app.tenant_id)如何工作?

  • 自定义会话变量
  • current_setting
  • RLS 策略引用

RLS 策略常使用自定义会话变量(如 app.tenant_id)来标识当前租户。应用连接后执行 SET app.tenant_id = '123'; 或 SET LOCAL app.tenant_id = '123';,RLS 策略中用 current_setting('app.tenant_id') 读取。这种方式让同一个数据库连接能按租户切换数据范围。注意:会话变量由应用设置,需防伪造(应用层控制),且 SET LOCAL 只在事务内生效。这是 RLS 多租户隔离的常见模式。

自定义 GUC(自定义配置参数)作为 RLS 的租户上下文。需确保应用正确设置,避免租户间越权。

-- 应用设置租户上下文
SET app.tenant_id = '123';
-- RLS 策略中读取
CREATE POLICY p ON orders USING (tenant_id = current_setting('app.tenant_id')::int);
#
★★

39. 领域事件(Domain Event)的存储?

领域事件(Domain Event)如何存储?

  • 领域事件概念
  • 存储方式
  • 事件溯源

领域事件(Domain Event)表示领域中的已发生事实(如订单已支付)。存储方式:①事件表(event log):把事件序列化(JSON)存入事件表,含事件类型、聚合 ID、时间戳、载荷;②事件溯源(Event Sourcing):以事件为唯一数据源,聚合状态由事件重放得到,事件表即事实来源;③消息队列(Kafka)+ 落库:事件先发消息队列,再异步落库。存储需保证事件不可变、有序、可追溯。

领域事件存储是事件驱动架构与事件溯源的基础。事件表 + 事件溯源能重建聚合状态。

CREATE TABLE domain_events (
    id BIGSERIAL PRIMARY KEY,
    aggregate_id UUID, event_type TEXT,
    payload JSONB, occurred_at TIMESTAMP
);
#
★★

40. 自然键与代理键(Surrogate Key)的取舍,业务变更对系统的影响?

自然键与代理键(Surrogate Key)的取舍是什么?业务变更对系统的影响?

  • 自然键
  • 代理键
  • 业务变更影响

自然键(Natural Key)用业务属性做主键(如身份证号、邮箱),有业务意义、无需额外列,但业务属性可能变化(邮箱变更需更新主键)、复杂、跨系统不一致。代理键(Surrogate Key)是系统生成的键(自增 ID、UUID),无业务意义、稳定、简单,但需额外列、查询常需关联自然键。核心取舍:业务变更影响——自然键会随业务变化而被迫改动,影响所有引用;代理键稳定,业务变更只影响自然键列本身。数据库常用代理键做主键,自然键加唯一约束。

代理键解耦业务与主键,稳定性好,是推荐做法。自然键用于业务唯一性校验。

-- 代理键主键 + 自然键唯一约束
CREATE TABLE users (
    id BIGINT PRIMARY KEY,          -- 代理键
    email VARCHAR(255) UNIQUE       -- 自然键
);
#
★★

41. 派生列(Derived Column)的应用,用户统计、订单总金额?

派生列(Derived Column)的应用是什么?用户统计、订单总金额如何用派生列?

  • 派生列概念
  • 用户统计
  • 订单总金额

派生列(Derived Column)存储可由其他字段计算得出的值,用于加速查询。应用:①订单总金额:订单表存 total = qty * price,避免每次聚合计算;②用户统计:用户表存订单数、总消费额等,避免每次统计。派生列需保持与源数据一致:更新源数据时同步更新派生列(通过触发器、应用事务或批处理)。PostgreSQL 支持生成列(GENERATED ALWAYS AS),数据库自动维护。适合读多写少、需频繁统计的场景。

派生列是反范式的典型,用空间换计算。生成列由数据库自动维护,避免手动同步。

-- PostgreSQL 生成列
CREATE TABLE orders (
    id INT PRIMARY KEY, qty INT, price NUMERIC,
    total NUMERIC GENERATED ALWAYS AS (qty * price) STORED
);
#
★★

42. 冗余字段的最终一致性,消息队列、CDC?

冗余字段的最终一致性如何保证?消息队列、CDC 如何应用?

  • 冗余字段一致性
  • 消息队列
  • CDC

反范式冗余字段需保证一致性。若不能在同一事务内更新,则采用最终一致性机制:①消息队列:源数据变更后发消息,消费者异步更新冗余字段,实现最终一致;②CDC(Change Data Capture):捕获源表变更,同步更新冗余字段;③批处理/定时任务:定期重算冗余字段。这些方式允许短暂不一致,但最终收敛。适合读多写少、可容忍轻微延迟的场景。同一事务内更新则强一致。

冗余字段的一致性维护是反范式的核心成本。消息队列与 CDC 是异步最终一致的常用手段。

-- 源表变更后发消息(应用层)
-- 消费者更新冗余字段
UPDATE orders SET customer_name = 'Alice' WHERE customer_id = 1;
-- 或 CDC 捕获变更后同步
#
★★

43. 雪花模型(Snowflake Schema)的取舍?

雪花模型(Snowflake Schema)的取舍是什么?

  • 雪花模型结构
  • 规范化维度
  • 与星型对比

雪花模型(Snowflake Schema)是星型模型的扩展,维度表进一步规范化(如把客户维度拆成客户、城市、省份多张表,形成层级)。优点:规范化高、消除维度冗余、节省存储、维度更新一致。缺点:查询需更多 JOIN、层级更深、性能下降、查询复杂。与星型相比,雪花更规范但查询慢,星型更反范式但查询快。取舍:数据量小、存储敏感、维度层级稳定用雪花;查询性能优先、维度扁平用星型。

雪花模型是星型的规范化版本,权衡"存储 vs 查询性能"。数据仓库场景常选星型(查询快)。

-- 雪花:维度拆成多表
CREATE TABLE dim_city (id INT PRIMARY KEY, city TEXT, province_id INT);
CREATE TABLE dim_province (id INT PRIMARY KEY, province TEXT);
-- 客户表引用城市
CREATE TABLE dim_customer (id INT PRIMARY KEY, name TEXT, city_id INT);
#
★★

44. 大宽表与列存数据库的协同,Parquet、ORC、ClickHouse?

大宽表与列存数据库如何协同?Parquet、ORC、ClickHouse 是什么?

  • 大宽表与列存
  • Parquet、ORC
  • ClickHouse

大宽表在列存引擎中表现优异:列存按列存储、只读所需列(列裁剪)、高压缩比,适合宽表的分析查询。Parquet、ORC 是列式存储文件格式(Hadoop 生态),压缩率高、支持列裁剪与谓词下推,常用于数据湖。ClickHouse 是列式 OLAP 数据库,MergeTree 引擎按列存储、向量化执行,适合宽表聚合分析。协同:大宽表(事实表)用列存/列式格式存储,OLAP 分析引擎查询,避免行存的 IO 浪费。

大宽表与列存天然契合:列存按列裁剪、高压缩,适合宽表分析。Parquet/ORC 是列式文件,ClickHouse 是列式数据库。

-- ClickHouse 建宽表
CREATE TABLE wide_events (
    event_id UInt64, user_id UInt64,
    col1 String, col2 String, ...
) ENGINE = MergeTree ORDER BY event_id;
#
★★

45. 大宽表(Wide Table)的应用场景,数据仓库、用户画像、订单事实表?

大宽表(Wide Table)的应用场景有哪些?数据仓库、用户画像、订单事实表?

  • 大宽表场景
  • 数据仓库
  • 用户画像

大宽表(Wide Table)把大量属性/字段合并到一张表,应用场景:①数据仓库:事实表包含大量度量与维度键,便于分析;②用户画像:一张表存用户的性别、年龄、行为、偏好等大量特征,便于标签查询与模型训练;③订单事实表:订单 + 明细 + 关联信息聚合,减少 JOIN。大宽表提升查询性能(少 JOIN)但加列成本高、列多。结合列存引擎与分区,可缓解列多问题。

大宽表适合"分析型、读多、字段多"场景,尤其在数据仓库与用户画像中减少 JOIN。大表加列需谨慎。

-- 用户画像宽表
CREATE TABLE user_profile (
    user_id BIGINT PRIMARY KEY,
    age INT, gender CHAR(1), city VARCHAR(50),
    total_orders INT, total_spend NUMERIC, ...
);
#
★★

46. 稀疏数据的 EAV 模型(Entity-Attribute-Value)的约束冲突?

稀疏数据的 EAV 模型(Entity-Attribute-Value)有什么约束冲突?

  • EAV 模型
  • 约束缺失
  • 查询复杂度

EAV(实体-属性-值)模型把属性作为行存储,适合稀疏/动态属性。但存在约束冲突:①无法定义强类型约束:value 列统一为字符串,无法区分类型,无法加非空、唯一等约束;②查询复杂:需自连接(转置)取多个属性,查询复杂、性能差;③无法用普通列索引高效查询;④数据完整性弱:无法保证特定属性存在。因此 EAV 只在属性极稀疏、动态时才用,现代更推荐 JSONB 或宽表。

EAV 的约束冲突核心是"牺牲类型与约束换灵活性"。JSONB 是更现代的替代。

CREATE TABLE eav (
    entity_id INT, attr TEXT, value TEXT,
    PRIMARY KEY (entity_id, attr)
);
-- 查询多个属性需自连接
SELECT e1.value, e2.value FROM eav e1
JOIN eav e2 ON e1.entity_id = e2.entity_id
WHERE e1.attr='age' AND e2.attr='city';
#
★★

47. 何时使用文档数据库(MongoDB),可变 Schema、嵌套结构?

何时使用文档数据库(MongoDB)?可变 Schema、嵌套结构?

  • 可变 schema
  • 嵌套结构
  • 适用场景

使用文档数据库(MongoDB)的场景:①可变 Schema:数据结构不稳定、字段频繁变化,文档可灵活增减字段;②嵌套结构:数据天然嵌套(如订单含明细、用户含地址),文档内嵌避免 JOIN;③快速迭代:原型开发、schema 演化快;④水平扩展:MongoDB 分片方便。不适用:强事务、强约束、复杂 JOIN、固定的关系型数据。选择依据:schema 稳定性、数据结构形态、事务需求。

文档数据库适合"schema 多变、数据嵌套、不依赖 JOIN"的场景。关系型数据用 SQL 库更合适。

// 可变 schema:文档可动态增减字段
db.users.insertOne({ name: "Alice", age: 30, extra: { prefer: "x" } });
// 嵌套结构:订单内嵌明细
db.orders.insertOne({ id: 1, items: [{ sku: "A", qty: 2 }] });
#
★★

48. 文档数据库模拟关系数据库,嵌入式文档 vs 引用(DBRef)?

文档数据库如何模拟关系数据库?嵌入式文档 vs 引用(DBRef)?

  • 嵌入式文档
  • 引用 DBRef
  • 模拟关系

文档数据库用两种方式模拟关系:①嵌入式文档(Embedded):把关联数据内嵌到文档中,相当于反范式,读一次取回、无关联查询,但数据重复、更新需同步;②引用(Reference/DBRef):文档中存对方 ID,相当于外键,数据唯一、无重复,但查询需多次查找或 $lookup 关联。DBRef 是 MongoDB 的引用约定(含集合名+ID)。选择:关系紧密、常一起读用嵌入式;数据独立、被多处引用用引用。对应关系模型的"反范式 vs 规范"。

嵌入式与引用是文档模型表达关系的两种方式,分别对应反范式与规范化思想。

// 嵌入式:订单内嵌客户
{ order: { id: 1, customer: { name: "Alice" } } }
// 引用:DBRef
{ order: { id: 1, customer: { $ref: "customers", $id: 100 } } }
#
★★

49. MongoDB 在 OLTP 场景的取舍?

MongoDB 在 OLTP 场景的取舍是什么?

  • OLTP 特点
  • MongoDB 优势
  • 事务与一致性

MongoDB 在 OLTP 场景的取舍:优势:高读写吞吐、灵活 schema、水平扩展、文档原子操作(单文档 ACID)、适合读多写多、嵌套数据。劣势:多文档事务(4.0+)较新、语义与性能有局限;无强约束(外键、非空);复杂 JOIN 与聚合弱于 SQL;一致性支持(可配置)。适合:内容/目录、用户资料、实时数据等灵活结构;不适合:强事务、强约束、复杂关联的金融/订单核心。OLTP 选型需权衡灵活性 vs 事务强度。

MongoDB 适合中等事务要求、灵活 schema 的 OLTP,强一致/强约束场景倾向 SQL 库。

// MongoDB 单文档原子操作
db.accounts.updateOne({ _id: 1 }, { $inc: { balance: -100 } });
#
★★

50. PostgreSQL BYPASSRLS 属性的作用,拥有该属性的角色如何绕过行级安全(RLS)策略,与超级用户权限的差异及安全边界?

PostgreSQL BYPASSRLS 属性的作用是什么?与超级用户权限的差异及安全边界?

  • BYPASSRLS 属性
  • 绕过 RLS
  • 与超级用户差异

BYPASSRLS 是 PostgreSQL 角色属性,拥有该属性的角色跳过所有行级安全(RLS)策略,直接访问所有行。它与超级用户权限类似但更细粒度:超级用户默认绕过 RLS 且拥有全部权限;BYPASSRLS 只让指定角色绕过 RLS,不授予其他超级用户权限。安全边界:普通角色不应授予 BYPASSRLS,因为会破坏租户隔离;只有当角色确实需要访问全部数据(如管理/ETL)时才授予。表 owner 默认也绕过 RLS,除非 FORCE ROW LEVEL SECURITY。

BYPASSRLS 是 RLS 的逃逸通道,需谨慎授予。区分超级用户与 BYPASSRLS 是安全设计要点。

-- 授予 BYPASSRLS
ALTER ROLE admin_user BYPASSRLS;
-- 回收
ALTER ROLE admin_user NOBYPASSRLS;
#
★★

51. MySQL 多 schema 的限制?

MySQL 多 schema 有什么限制?

  • MySQL schema 与数据库
  • 多 schema 限制
  • 跨 schema 查询

MySQL 中 schema 与 database 是等价的(database 即 schema),没有 PostgreSQL 那样"一个实例内多 schema"的独立概念。限制:①每租户一个 schema 即每租户一个 database,连接需指定 database,跨库查询需前缀(db.table),且跨库事务/外键支持有限;②数据库数量多时元数据、连接管理复杂;③schema 隔离粒度粗。因此 MySQL 多租户更常用共享表 + tenant_id 或每租户一个 database,而非 schema-per-tenant。

MySQL 的 schema=database,多 schema 隔离实际是多库隔离,粒度与成本与 PG 不同。

-- MySQL 中 database 即 schema
CREATE DATABASE tenant_123;
-- 跨库查询需前缀
SELECT * FROM tenant_123.orders;
#
★★

52. RLS 与 ORM 的协同?

RLS 与 ORM 如何协同?

  • RLS 与 ORM
  • 会话设置
  • 应用集成

RLS 与 ORM 协同:ORM(如 Hibernate、MyBatis、JPA)执行 SQL 时,RLS 在数据库层自动过滤,无需 ORM 修改查询。协同要点:①应用连接时设置租户上下文(SET app.tenant_id),RLS 策略据此过滤;②ORM 连接池需处理会话变量(每个连接设置租户);③ORM 不感知 RLS,但 RLS 保证安全;④ORM 的批量操作、关联查询需确保满足 RLS 条件。RLS 提供数据库层兜底,ORM 提供对象层面映射,两者互补。

RLS 与 ORM 协同的关键是"应用设置租户上下文 + RLS 数据库层过滤",ORM 无需感知 RLS。

// 连接后设置租户上下文
SET app.tenant_id = '123';
// ORM 正常查询,RLS 自动过滤
List<Order> orders = orderRepository.findAll();
#
★★

53. tenant_id 列的设计准则?

tenant_id 列的设计准则是什么?

  • tenant_id 位置
  • 索引
  • 查询强制

tenant_id 列的设计准则:①所有租户相关表都应包含 tenant_id 列;②tenant_id 应纳入主键或作为复合索引前缀(如 PRIMARY KEY (tenant_id, id)),保证租户内查询高效;③SELECT/UPDATE/DELETE 必须带 tenant_id 条件,配合 RLS 强制;④tenant_id 用 INT/BIGINT 或 UUID,避免字符串;⑤分区可按 tenant_id 或时间。目标是让租户隔离在查询与索引层面都高效且安全。

tenant_id 是共享表多租户的核心,设计原则是"索引前缀 + 查询强制 + RLS 兜底"。

CREATE TABLE orders (
    tenant_id INT, id BIGINT, amount NUMERIC,
    PRIMARY KEY (tenant_id, id)      -- tenant_id 作索引前缀
);
-- 查询必须带 tenant_id
SELECT * FROM orders WHERE tenant_id = 1 AND id = 100;
#
★★

54. 多租户 SaaS 架构模式?

多租户 SaaS 架构模式有哪些?

  • 三种模式
  • 选择依据
  • 演进

多租户 SaaS 架构模式:①共享表(Shared Table):所有租户一张表,tenant_id 区分,成本最低、扩展好,隔离靠 RLS/应用;②数据库每租户(Database-per-Tenant):每租户一个库,隔离最强、成本最高;③Schema-per-Tenant:每租户一个 schema,折中。此外还有"共享表 + 分区"、混合模式。选择依据:租户数量、隔离要求、成本、合规。很多 SaaS 从共享表起步,随隔离要求提升演进到多 schema/多库。

多租户模式是 SaaS 架构的核心决策,在"隔离性、成本、扩展性"间权衡。

-- 共享表模式
CREATE TABLE orders (tenant_id INT, ...);
-- 多 schema 模式
CREATE SCHEMA tenant_123;
-- 多库模式
CREATE DATABASE tenant_123;
#
★★

55. 雪花 ID 的趋势递增特性与索引性能?

雪花 ID 的趋势递增特性与索引性能是什么?

  • 雪花 ID 结构
  • 趋势递增
  • 索引性能

雪花 ID(Snowflake ID)是 64 位整数,由时间戳(41 位)+ 机器 ID(10 位)+ 序列号(12 位)组成。时间戳在高位使 ID 随时间"趋势递增"(大致单调,但非严格单调,因为机器 ID 与序列号)。这种趋势递增特性对数据库索引友好:插入顺序接近递增,减少 B-tree 页分裂、索引碎片化,提升写入性能,优于完全随机的 UUID v4。适合分布式系统生成有序主键。

雪花 ID 结合分布式唯一性与时间有序性,是分布式有序主键的经典方案,兼顾性能与分布式。

-- 雪花 ID 作为主键(应用层生成)
CREATE TABLE t (id BIGINT PRIMARY KEY, ...);
-- 插入顺序接近递增,索引友好
#
★★

56. INT 与 BIGINT 的选择?

INT 与 BIGINT 如何选择?

  • 范围
  • 存储
  • 场景

INT 占 4 字节,范围约 ±21 亿(约 20 亿),适合数据量小、无需超大规模的场景。BIGINT 占 8 字节,范围极大,适合大数据量、高并发、自增可能溢出、分布式(雪花 ID)场景。选择依据:数据量上限、扩容预期。若数据可能超过 21 亿行或需容纳雪花 ID,用 BIGINT。INT 更省空间、索引更小,但需估算。OLTP 高并发常选 BIGINT 避免溢出。

INT 与 BIGINT 的关键是"范围是否够用"。2 亿+ 行或需分布式 ID 时用 BIGINT。

CREATE TABLE t (id INT PRIMARY KEY);    -- 最多约 21 亿
CREATE TABLE t2 (id BIGINT PRIMARY KEY); -- 范围更大
#
★★

57. Snowflake ID 的时钟回拨?

Snowflake ID 的时钟回拨问题是什么?如何解决?

  • 时钟回拨
  • ID 重复
  • 解决策略

雪花 ID 依赖时间戳,若系统时钟回拨(NTP 校正、时钟漂移),可能导致生成的时间戳小于之前,从而产生重复 ID(同一时间戳+机器+序列号冲突)。解决策略:①检测回拨,若回拨则等待时钟追赶或拒发;②用序列号/特殊位处理;③准备备用时钟同步;④回拨时用页面缓存或等待。雪花 ID 生成器需处理时钟回拨以保证唯一性。这是分布式 ID 的经典问题。

时钟回拨是雪花 ID 的唯一性风险,需在生成器内检测与规避。

# 伪代码:检测时钟回拨
if timestamp < last_timestamp:
    # 时钟回拨,等待或抛异常
    raise Exception("clock moved backwards")
#
★★

58. UUID v7 与雪花 ID 的对比?

UUID v7 与雪花 ID 的对比是什么?

  • UUID v7
  • 雪花 ID
  • 差异

UUID v7 与雪花 ID 都是"时间有序 + 随机"的分布式 ID:UUID v7 是 128 位,含时间戳(48 位)+ 随机位,符合 UUID 标准,兼容性好、可离线生成;雪花 ID 是 64 位,时间戳(41 位)+ 机器 ID + 序列号,更紧凑、可自定义(含机器/序列信息)。UUID v7 优势:标准、通用、无需分配机器 ID;雪花 ID 优势:64 位更小、可含业务信息。两者都趋势递增、索引友好。选择:标准兼容用 UUID v7,紧凑/自有架构用雪花 ID。

UUID v7 与雪花 ID 都是现代且有序的分布式 ID,区别在位数、标准符合性与可自定义性。

-- UUID v7(128 位)
SELECT uuidv7();
-- 雪花 ID(64 位,应用层生成)
#
★★

59. 主键与 UUID v1 的隐私问题?

主键与 UUID v1 的隐私问题是什么?

  • UUID v1 结构
  • MAC 地址泄露
  • 隐私风险

UUID v1 由时间戳 + 节点(MAC 地址)+ 时钟序列组成,其中节点部分使用网卡 MAC 地址,会泄露生成机器的硬件信息,且时间戳可推测生成时间,存在隐私与安全风险。因此在对外暴露主键(如 URL、API)时,不建议用 UUID v1。UUID v4(随机)或 v7(时间有序)不包含 MAC 地址,隐私更安全。主键若暴露给外部,应避免可预测或含机器信息的 ID。

UUID v1 的 MAC 地址成分是隐私与安全缺陷,现代应用推荐 v4/v7。

-- UUID v1 含 MAC 地址,有隐私风险
-- 推荐 v4(随机)或 v7(时间有序)
CREATE TABLE t (id UUID PRIMARY KEY DEFAULT gen_random_uuid()); -- v4
#
★★

60. OLTP 与 OLAP 的范式差异?

OLTP 与 OLAP 的范式差异是什么?

  • OLTP 范式
  • OLAP 范式
  • 差异

OLTP(在线事务处理)强调数据一致性、事务与更新效率,通常采用高范式(3NF/BCNF),消除冗余、避免更新异常,表较规范、插入更新高效。OLAP(在线分析处理)强调查询与分析性能,通常采用反范式(星型/雪花模型、宽表、冗余、预聚合),减少 JOIN、加速聚合。差异:OLTP 规范化、写多、一致性强;OLAP 反范式、读多、聚合快。数据仓库把 OLTP 数据经过 ETL 转为 OLAP 星型模型。

OLTP 与 OLAP 的范式选择相反:OLTP 靠规范化保证一致性,OLAP 靠反范式提升查询性能。

-- OLTP:3NF 规范化
CREATE TABLE customers (id INT PRIMARY KEY, name TEXT);
CREATE TABLE orders (id INT PRIMARY KEY, customer_id INT REFERENCES customers(id));
-- OLAP:星型模型反范式
CREATE TABLE dim_customer (id INT PRIMARY KEY, name TEXT, city TEXT);
#
★★

61. EAV(实体-属性-值)模型的查询复杂度,动态属性带来的多表连接与类型转换代价,何时该用 JSONB 或宽表替代?

EAV(实体-属性-值)模型的查询复杂度是什么?何时该用 JSONB 或宽表替代?

  • EAV 查询复杂度
  • 多表连接
  • JSONB/宽表替代

EAV 模型的查询复杂度高:取多个属性需多次自连接(转置),属性多时查询复杂、JOIN 多、性能差;value 统一为字符串,需类型转换(CAST)才能比较数值/日期,增加代价;无法使用普通列索引高效查询。因此当属性数量适中、结构相对固定时,应改用 JSONB(半结构化,可索引、可路径查询)或宽表(每属性一列,类型约束、可索引)。EAV 只在属性极稀疏、动态时才适用。

EAV 的查询复杂度源于"属性行化",JSONB 与宽表是更优替代。取舍关键在于属性多少与结构稳定性。

-- EAV 查询多个属性需自连接 + 类型转换
SELECT e1.entity_id, (e1.value)::int AS age, e2.value AS city
FROM eav e1 JOIN eav e2 ON e1.entity_id = e2.entity_id
WHERE e1.attr='age' AND e2.attr='city';
-- 替代:JSONB
SELECT (attrs->>'age')::int, attrs->>'city' FROM products WHERE ...;
#

62. 宽表在 ETL 中的处理策略,动态加列对管道的影响、Schema-on-Write 与 Schema-on-Read 的取舍,以及列裁剪与行存/列存的选择?

宽表在 ETL 中的处理策略是什么?动态加列对管道影响、Schema-on-Write 与 Schema-on-Read 取舍、列裁剪与行存/列存选择?

  • 动态加列
  • Schema-on-Write vs Schema-on-Read
  • 列裁剪与行存/列存

宽表在 ETL 中的处理策略:①动态加列:宽表列经常变化,动态加列会影响 ETL 管道(需更新 schema、历史数据),应制定加列流程或用柔性 schema(JSON 列);②Schema-on-Write(写入时校验 schema)vs Schema-on-Read(读取时解析 schema):宽表多变时倾向 Schema-on-Read(如 Parquet/JSON/pandas),灵活、改列无需重写;③列裁剪与行存/列存:宽表列多,高并发点查用行存,分析/聚合用列存(列裁剪、压缩)。选择取决于查询模式与 schema 稳定性。

宽表 ETL 的关键是"schema 演进"与"存储格式"。Schema-on-Read + 列存适合宽表分析。

-- 写入时校验 schema(行存)
-- 读取时解析 schema(Parquet/JSON)
-- 列存:只读需要的列
SELECT col1, col2 FROM wide_table;  -- 列存只读这两列
#

63. 宽表的分桶(Bucketing)?

宽表的分桶(Bucketing)是什么?

  • 分桶概念
  • 分区与分桶
  • 优化

分桶(Bucketing)指在大表内部按某列(如 ID、哈希)将数据划分为固定数量的桶(bucket),桶内数据按分桶键组织。常用于 Hive、Spark 等分布式计算:分桶后可加速 JOIN 与抽样(桶内数据可并行处理、桶间 JOIN 更高效)。分桶与分区(partition)不同:分区按目录/列值分,分桶按哈希分到固定桶数。宽表分桶可减少数据倾斜、提升查询与聚合性能。

分桶是分布式表数据组织的优化手段,与分区互补。分桶键选择影响数据分布均匀性。

-- Hive 分桶
CREATE TABLE t (id INT, ...) CLUSTERED BY (id) INTO 16 BUCKETS;
#

64. 稀疏列的存储格式(Parquet)?

稀疏列的存储格式(Parquet)如何优化?

  • Parquet 列存
  • NULL 压缩
  • 稀疏列优化

Parquet 是列式存储格式,对稀疏列(大量 NULL)有天然优化:按列存储时,NULL 不占具体数值空间,结合 RLE(行程编码)、dictionary(字典)等压缩,稀疏列几乎不占空间;且列裁剪只读需要的列,不读取 NULL 列。相比行存,Parquet 对宽表/稀疏表的存储与查询效率都高。适合数据湖、分析场景的稀疏数据。

Parquet 列存 + 压缩对稀疏列友好,是数据湖宽表/稀疏表的推荐格式。

-- Parquet 列存:稀疏列 NULL 几乎不占空间,列裁剪只读所需列
-- 读取时只取需要的列
#

65. JSON Schema 在数据库中的应用,用 CHECK 约束与 jsonb_typeof 保障半结构化数据质量,与文档数据库的 schema 校验差异及适用场景?

JSON Schema 在数据库中的应用是什么?CHECK 约束与 jsonb_typeof 如何保障半结构化数据质量?

  • JSON Schema
  • CHECK 约束
  • jsonb_typeof

在数据库(如 PostgreSQL)中可用 CHECK 约束结合 jsonb_typeof 校验 JSONB 字段的数据质量:如 jsonb_typeof(data->'age') = 'number' 检查类型,或 data @> '{"required_field": ""}' 检查必填字段。这样在关系数据库内对半结构化数据施加约束,保证质量。与文档数据库(MongoDB)的 schema 校验差异:MongoDB 用 JSON Schema 校验器也可校验,但关系库的 CHECK 更细粒度、可结合其他约束。JSON Schema 一致性校验适合"半结构化但需质量保证"的场景。

CHECK + jsonb_typeof 是关系库对 JSONB 施加约束的常用手段,弥补 JSONB 弱类型缺陷。

CREATE TABLE t (
    id INT PRIMARY KEY,
    data JSONB,
    CHECK (jsonb_typeof(data->'age') = 'number')
);
#

66. 文档模型的 Schema 演进?

文档模型的 Schema 演进是什么?

  • 文档模型 schema 演进
  • 灵活 schema
  • 演进策略

文档模型(如 MongoDB)的 schema 演进指其灵活的 schema 使字段增减无需迁移整表:新增字段只需新文档带该字段,旧文档可缺省;可加 schema 校验(可选)或数据迁移脚本。相比关系模型的 ALTER TABLE(需锁定、重建),文档模型演进成本低、无停机。但需注意:无强制 schema 可能导致数据不一致,需用校验器、文档规范或迁移工具(如演进的版本字段)保持质量。演进策略:向前兼容、版本字段、渐进迁移。

文档模型 schema 演进的优点是灵活、低迁移成本,代价是需要通过校验/规范控制质量。

// 文档模型:新字段无需迁移
db.users.insertOne({ name: "Alice", age: 30, newField: "x" });
// MongoDB 校验器
db.createCollection("users", { validator: { $jsonSchema: { required: ["name"] } } });