数据库测试核心

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

1. 数据库测试的分层策略,Schema 验证、数据完整性、查询正确性、性能基准各自如何测试?

数据库测试如何进行分层设计?Schema 验证、数据完整性、查询正确性、性能基准这四个层次分别该用什么方法测试?

  • 是否理解数据库测试的多层次模型(Schema / 数据 / 查询 / 性能)
  • 各层次测试工具与验证手段的选型
  • 分层与业务集成测试的关系

数据库测试应自下而上分层推进。第一层 Schema 验证:用数据库迁移工具(Flyway/Liquibase)的校验命令或 information_schema 查询,断言表、字段类型、约束、索引、默认值存在且符合预期,可用 maven-sql-plugin 或断言库验证 DDL。第二层数据完整性:用主键、唯一、外键、CHECK、NOT NULL 约束以及触发器的行为验证,插入非法数据断言被拒绝,插入合法数据断言通过。第三层查询正确性:用真实数据或受控测试数据比对查询结果,涉及 JOIN、聚合、分组、子查询、窗口函数等,可结合快照测试或结果集断言。第四层性能基准:用固定数据量、相同硬件环境下的 EXPLAIN 计划、执行耗时、内存与锁等待指标做基准,趋势对比而非单次绝对数值。各层关注点不同,应分别建立测试集,避免混为一谈。

分层的目的在于隔离关注点:Schema 错误是"建表就错了",数据问题是"数据本身不合法",查询问题是"SQL 写错",性能问题是"数据量上来后变慢"。只有分层才能快速定位缺陷归属,也便于把测试嵌入 CI 的早期阶段。

@Test
void schemaTest() {
    // 验证表存在且字段类型正确
    assertThat(selectOne("SELECT data_type FROM information_schema.columns " +
        "WHERE table_name='orders' AND column_name='amount'"))
        .isEqualTo("DECIMAL");
}
@Test
void integrityTest() {
    // 违反外键约束应被拒绝
    assertThatThrownBy(() -> insert("INSERT INTO orders(customer_id) VALUES (999999)"))
        .isInstanceOf(SQLIntegrityConstraintViolationException.class);
}
#
★★★

2. 数据库迁移(Migration)测试,如何验证 Flyway/Liquibase 迁移脚本的正确性、可回滚性和幂等性?

如何验证 Flyway 或 Liquibase 数据库迁移脚本的正确性、可回滚性和幂等性?

  • 迁移脚本的 apply 与 rollback 验证
  • 幂等性(重复执行不产生副作用)的验证
  • 迁移历史与版本校验的可靠性

迁移测试首先验证"正确性":每个迁移脚本在干净库上从头执行能成功,且目标表结构、数据、约束符合预期。其次验证"可回滚性":每个迁移(尤其 Liquibase 的 rollback 标签或 Flyway 的 undo 脚本)有对应的回滚脚本,测试先 apply 再 rollback,断言结构与数据恢复到迁移前状态。再验证"幂等性":Flyway 通过 schema history 表保证每个版本只执行一次,Liquibase 通过 changelog 的 checksum 校验;测试需在版本已应用后重复执行,断言不重复插入、不报错、checksum 一致。实际做法是:用 Testcontainers 起一个空库,依次执行全部迁移脚本,断言最终 schema 与预置的期望 schema 比对一致;再新增一个"临时迁移"测试 apply→rollback 往返。对 checksum 变更导致的失败也要验证,防止生产环境因脚本被修改而启动失败。

迁移是"改一次、到处执行"的产物,任何一处执行失败都会阻塞生产发布。正确性、可回滚性、幂等性三者分别对应"能装上""能回退""重复执行安全",是迁移脚本质量的三条底线,最好在 CI 中用全新数据库跑通全量迁移。

// 用 Testcontainers 在全新数据库上验证全量迁移可执行
@Test
void fullMigrationRunsOnCleanDb() {
    try (PostgreSQLContainer<?> db = new PostgreSQLContainer<>("postgres:15")) {
        db.start();
        Flyway.configure().dataSource(db.getJdbcUrl(), db.getUsername(), db.getPassword())
            .load().migrate();
        // 断言关键表存在
        assertThat(tableExists(db, "orders")).isTrue();
    }
}
#
★★★

3. Testcontainers 在数据库集成测试中的应用,如何为每个测试用例启动隔离的数据库实例?

Testcontainers 如何在数据库集成测试中为每个测试用例启动隔离的数据库实例?

  • Testcontainers 的容器生命周期管理
  • 隔离级别(类级 vs 用例级)与速度权衡
  • 与 Flyway 初始化、连接池的配合

Testcontainers 通过 Docker 为每个测试创建真实的数据库容器,保证与生产环境一致。常见做法是:用 @Testcontainers@Container 注解定义一个 PostgreSQLContainer,配合 @DynamicPropertySource 把容器的 JDBC 地址注入 Spring 配置;或采用 Spring Boot 的 @ServiceConnection 自动注入连接细节,或手动 set 连接参数。隔离级别上,为每个测试类共享一个容器(static 容器 + 每条用例用事务回滚或 TRUNCATE 重置数据)是性能与隔离的平衡;若要求绝对隔离,可在每个用例内各自 start() 一个容器,但代价是启动开销大、测试慢。初始化时通常先 Flyway/Liquibase 迁移建表,再插入测试数据。镜像与数据卷可复用,减少反复拉取。

相比嵌入式数据库(H2),Testcontainers 用真实数据库,能暴露方言差异、锁行为、约束校验等真实语义,是数据库集成测试的主流方案。隔离粒度是核心权衡:类级共享容器 + 事务回滚兼顾速度与隔离,用例级独立容器保证最强隔离但慢。

@Testcontainers
@SpringBootTest
class OrderRepositoryIT {
    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");

    @DynamicPropertySource
    static void props(DynamicPropertyRegistry r) {
        r.add("spring.datasource.url", postgres::getJdbcUrl);
        r.add("spring.datasource.username", postgres::getUsername);
        r.add("spring.datasource.password", postgres::getPassword);
    }
}
#
★★★

4. 数据库事务测试,如何验证 ACID 属性在并发场景下的正确性?如何测试死锁检测和回滚行为?

如何验证事务的 ACID 属性在并发场景下的正确性?如何测试死锁检测和回滚行为?

  • 原子性、一致性、隔离性、持久性各自的验证方法
  • 并发隔离级别的行为验证
  • 死锁检测与回滚的测试手法

ACID 验证需分属性设计测试。原子性:构造一个中途失败的多语句事务,断言所有语句全部回滚、无部分提交。一致性:事务前后不变量(如余额非负、库存总数守恒)保持成立。隔离性:用两个并发事务分别执行读、写、范围读,验证不同隔离级别下脏读、不可重复读、幻读是否出现;可用两个线程或多个连接同步执行,配合 SELECT ... FOR UPDATE、锁等待超时观察。持久性:事务提交后模拟进程崩溃或 kill -9 数据库,重启后断言已提交数据仍在。死锁测试:两个事务按相反顺序锁同一批资源,使其互相等待,断言数据库在超时后检测死锁并回滚其中一个事务,同时应用程序能捕获并重试。测试中常用固定线程数、Barrier 同步、SET innodb_lock_wait_timeout 等来控制并发时序。

并发事务测试的难点在于时序不可控,需要引入同步屏障使两个事务在预设时刻互相阻塞,从而稳定重现死锁或隔离异常。重点验证的是"数据库按预期行为响应",而不是单纯的业务结果。

// 死锁稳定重现:两个事务按相反顺序抢占两个资源
ExecutorService pool = Executors.newFixedThreadPool(2);
CountDownLatch t1 = new CountDownLatch(1), t2 = new CountDownLatch(1);
Future<?> f1 = pool.submit(() -> tx("UPDATE accounts SET bal=bal-1 WHERE id=1; "
    + await(t2) + "UPDATE accounts SET bal=bal+1 WHERE id=2;"));
Future<?> f2 = pool.submit(() -> tx("UPDATE accounts SET bal=bal-1 WHERE id=2; "
    + await(t1) + "UPDATE accounts SET bal=bal+1 WHERE id=1;"));
// 断言其中一个事务抛出 Deadlock 异常并被回滚
#
★★★

5. 数据一致性测试,跨表外键约束、触发器、存储过程在边界条件下的行为验证?

如何验证跨表的外键约束、触发器、存储过程在边界条件下的行为?

  • 外键约束的级联、拒绝、置空行为验证
  • 触发器在插入/更新/删除时的副作用验证
  • 存储过程在空表、边界、异常输入下的行为

外键约束测试:验证引用不存在父行时插入子行被拒绝;验证删除被引用父行时按 ON DELETE CASCADE/RESTRICT/SET NULL 产生预期行为;验证更新父键时子表是否级联更新。触发器测试:构造插入/更新/删除事件,断言触发器写入的审计表、日志表或计算字段符合预期;测试触发器内的异常是否整体回滚主操作。存储过程测试:覆盖空表、单行、大量行、NULL 输入、边界值、重复调用、事务内失败等场景,断言返回结果集、输出参数、异常信息正确。边界条件尤其要覆盖外键环、自引用外键、触发器递归(多层触发器)等容易出错的场景。

一致性是数据库的"隐性约束",它不靠业务代码保证,而是靠 DDL 与数据库对象保证。测试的关键是围绕"什么情况下数据库会拒绝/级联/触发"来设计用例,主动验证数据库对象的语义与业务规则一致。

-- 验证外键级联删除
INSERT INTO customers(id) VALUES (1);
INSERT INTO orders(customer_id) VALUES (1);
DELETE FROM customers WHERE id = 1;  -- ON DELETE CASCADE
SELECT count(*) FROM orders WHERE customer_id = 1;  -- 期望 0
#
★★★

6. 如何对慢 SQL 优化做回归测试,用 EXPLAIN 计划快照对比还是耗时基线对比?

对慢 SQL 优化做回归测试时,应该用 EXPLAIN 计划快照对比还是耗时基线对比?

  • EXPLAIN 计划断言与耗时基线的优劣
  • 两者结合使用的策略
  • 避免性能测试的脆弱性

两者应结合使用,各司其职。EXPLAIN 计划快照对比:预设一个期望的访问方式(如走索引、type=ref/range、无 Using filesort),把 EXPLAIN 输出中的关键字段(type、key、rows、Extra)作为断言,能在小数据量、低耗时下快速、稳定地发现"索引失效"类回归,缺点是它对数据量变化不敏感。耗时基线对比:在固定数据量、固定并发下测量执行时间,能发现数据量增长带来的性能退化,缺点是受硬件、负载环境影响,波动大、易脆弱。推荐做法:把 EXPLAIN 计划断言作为 CI 的快速回归门禁,把耗时基线作为慢速的、带统计容差的性能测试,并限制数据量规模、关闭无关负载、多次取中位数。

计划快照是"结构"层面的回归检测,稳定且快;耗时是"感受"层面的检测,真实但有噪声。二者互补,单靠耗时基线会因环境波动误报,单靠计划快照会漏掉数据量引起的退化。

// 断言查询走索引:EXPLAIN 中 key 不为空且 type 为 ref
String plan = selectOne("EXPLAIN SELECT * FROM orders WHERE customer_id=1");
assertThat(plan).contains("ref").contains("idx_orders_customer");
#
★★

7. 数据库性能测试,如何设计覆盖索引命中、全表扫描、锁竞争的 SQL 基准测试?

如何设计覆盖索引命中、全表扫描、锁竞争三类场景的 SQL 基准测试?

  • 覆盖索引命中的构造与验证
  • 全表扫描场景的构造
  • 锁竞争模拟与并发基准

覆盖索引命中基准:建立包含查询所需全部列的复合索引,构造 SELECT 列 FROM 表 WHERE 条件 的查询,使查询只读索引即可返回,用 EXPLAIN 断言 Using index 且无回表。全表扫描基准:构造无可用索引或 LIKE '%x%'、列上加函数等场景,用 EXPLAIN 断言 type=ALL,对比其在数据量增长下的耗时退化。锁竞争基准:固定并发连接数,让多个事务同时更新同一行或同一区间,用 SHOW ENGINE INNODB STATUSperformance_schema 观察锁等待时间、死锁次数,对比不同隔离级别与索引设计下的吞吐。基准测试要求固定数据量、固定连接池、固定并发线程数,多次运行取中位数并绘趋势。

三类基准分别对应"查询快""查询慢""并发被锁卡住"三种性能问题。设计基准的关键是让场景可复现、可量化——用 EXPLAIN 佐证访问方式,用指标体系(耗时、锁等待、吞吐)度量影响,从而支撑优化前后的对比。

#
★★

8. 数据库测试数据管理,如何生成符合业务约束的合成数据(如外键关系、唯一约束、CHECK 约束)?

如何生成符合外键关系、唯一约束、CHECK 约束等业务约束的合成测试数据?

  • 约束感知的数据生成策略
  • 数据生成工具与自定义生成器
  • 数据量与可复现性平衡

生成符合约束的合成数据需"约束感知"地生成。先按依赖顺序生成父表数据,再生成子表数据引用父表主键,保证外键关系成立;用唯一值生成器(自增、UUID、序列)保证唯一约束;用受控的值域生成器满足 CHECK 约束与枚举;对 NULL 约束、默认值、长度限制也需尊重。可借助 Faker、Datafaker(Java Faker)等库生成语义合理的名字、地址、时间,再配合自定义规则。为保证可复现,用固定种子(seed)随机数。工业级方案可用数据子集抽取(从生产环境抽样)或数据生成平台(如 Tonic、Synthea)生成符合统计分布的数据。数据量需分级:小集跑通功能、中集跑性能冒烟、大集跑压力。

约束是数据库的"法律",生成数据若不遵守会导致插入失败或测试失真。按依赖顺序 + 约束感知的生成器 + 固定种子,是最可控、可复现的做法,同时兼顾真实性与性能需要。

// 先生成父表,再生成引用父表外键的子表
for (int i = 0; i < n; i++) {
    int custId = insertCustomer(faker.idNumber().valid(), faker.name().fullName());
    insertOrder(custId, faker.number().randomDouble(2, 10, 10000)); // 满足 CHECK(amount>0)
}
#
★★

9. 分库分表场景的测试,如何验证路由规则、跨分片查询和数据迁移的正确性?

分库分表场景下如何验证路由规则、跨分片查询和数据迁移的正确性?

  • 分片键路由规则的正确性验证
  • 跨分片聚合查询的正确性
  • 数据迁移与重分片的验证

路由规则验证:构造不同的分片键取值,断言数据被正确路由到预期的库/表(可用中间件日志或 SHOW 验证实际落库位置),尤其验证分片键边界值、哈希取模、范围区间等边界情况。跨分片查询验证:覆盖广播、分散再聚合、跨片 JOIN 等场景,断言结果与不分片时的逻辑结果一致(可对比单库参照实现)。数据迁移验证:迁移或重分片后做全量比对,校验条数、主键、校验和(如对每行做哈希汇总)一致,无丢失、无重复、无错位;对增量迁移还要验证追平后的一致性。还要验证分片键缺失、分片键变更、跨分片事务等异常场景。

分库分表把"单库一致性"变成"多库一致性",测试核心是"路由对不对"和"迁移后对不对"。用参照实现做结果比对是跨分片查询的最可靠手段,边界的路由与迁移后的全量对账是关键风险点。

#
★★

10. ORM 与原生 SQL 的测试差异,如何验证 ORM 生成的 SQL 符合预期(如 N+1 查询检测)?

ORM 与原生 SQL 的测试有何差异?如何验证 ORM 生成的 SQL 符合预期并检测 N+1 查询?

  • ORM 生成 SQL 的验证方法
  • N+1 查询的检测手段
  • 与原生 SQL 测试的差异

原生 SQL 直接测试 SQL 正确性,而 ORM 测试关注的是"ORM 生成的 SQL"是否符合预期。验证方法:用 Hibernate 的 show_sql 或 SQL 监听器(如 datasource-proxy、p6spy)捕获实际执行的 SQL,断言其 JOIN 方式、条件、批处理、懒加载时机正确。N+1 检测:在查询一个集合父实体后,断言数据库执行语句总数(或 SELECT 数)在可接受范围内,避免"1 条主查询 + N 条子查询";可用 @BatchSize@EntityGraphjoin fetch 优化后复测。差异点:ORM 测试需覆盖懒加载/急加载、一级缓存与二级缓存、脏检查、级联保存等对象-关系映射行为,而原生 SQL 测试只需覆盖 SQL 语义。常用工具:Testcontainers 真库 + SQL 捕获代理 + 语句计数断言。

大部分数据库性能问题来自 ORM 生成的意外 SQL(N+1、全表扫描、笛卡尔积)。通过捕获并断言真实执行的 SQL 数量和形态,能在 ORM 层面精准拦截问题,这是原生 SQL 测试无法覆盖的。

// 捕获 SQL 并断言查询一个订单列表时没有 N+1
List<String> sqls = captureSql(() -> repository.findAllWithItems());
assertThat(sqls).anyMatch(s -> s.contains("from orders"));
assertThat(countOf(sqls, "from order_items")).isLessThanOrEqualTo(1);
#
★★

11. 数据库备份恢复测试,如何定期验证备份的完整性和恢复流程的可靠性?

如何定期验证数据库备份的完整性和恢复流程的可靠性?

  • 备份完整性的验证方法
  • 恢复演练的流程与指标
  • 定期化的自动化机制

备份恢复测试的核心是"定期做恢复演练",而不是只验证备份文件存在。完整性验证:比较备份文件大小与变化、校验和、备份日志无错误;更可靠的是把备份恢复到隔离环境,运行表/行数比对、校验和比对、应用冒烟查询,确认数据可读且一致。恢复流程测试:按生产恢复文档执行,测量 RTO(恢复时间目标)与恢复后的数据完整性,验证 PITR(时间点恢复)、增量备份叠加、归档日志重放等能力。要自动化:用 CI 定时任务每周在测试环境自动拉取最新备份→恢复→校验→生成报告,并在失败时告警。还需定期演练"演练本身"(如模拟备份文件损坏、日志丢失),验证检测机制有效。

备份只有在"能恢复"时才有价值。定期恢复演练是把备份从"存在"变成"可信"的关键,RTO/RPO 指标与自动化执行是让演练可持续、可度量、可告警的保障。

#
★★

12. 数据库事务的并发测试(脏读/幻读/死锁)如何在测试环境稳定复现?

如何在测试环境稳定复现脏读、幻读、死锁等并发事务问题?

  • 并发时序的同步控制
  • 隔离级别与锁行为的配合
  • 稳定复现的手段

稳定复现并发问题的关键是"人为控制时序",让两个事务在确定时刻互相阻塞。常用手段:事务内用 SELECT ... FOR UPDATELOCK 拿到锁,再借助 CountDownLatchBarrier、阻塞队列或 sleep 让两个事务在预定位置停住,等待彼此;配合 SET TRANSACTION ISOLATION LEVEL 固定隔离级别,并设置 innodb_lock_wait_timeout 把锁等待转成可控超时。脏读复现:在 READ UNCOMMITTED 下让一个事务写未提交、另一事务读同一行。幻读复现:在 REPEATABLE READ 下范围读两次,间隔插入新行。死锁复现:两个事务按相反顺序锁两个资源。稳定复现还要固定连接池、单线程逐个调度、禁用查询优化器干扰,必要时用真实数据库而非 H2。

并发问题本质是"时序问题",随机并行很难命中。用同步屏障把时序固定下来,测试就变成确定性的,可重复执行、可断言数据库的预期行为(回滚、抛异常、隔离级别下的可见性)。

// 用 CountDownLatch 固定两事务的时序,稳定复现死锁
CountDownLatch latch1 = new CountDownLatch(1), latch2 = new CountDownLatch(1);
// 事务A:锁id=1 → 等latch2 → 锁id=2
// 事务B:锁id=2 → 等latch1 → 锁id=1
#
★★

13. 测试数据中的时间敏感数据(如 "now"、时区)如何做确定性处理?

测试数据中的时间敏感数据(如 "now"、时区)如何做确定性处理?

  • 时间源的注入与固定
  • 时区一致性处理
  • 相对时间与边界时间的确定性

时间敏感数据确定性处理的核心是"可控的时间源"。做法:用可注入的时钟(Java 的 Clockjava.time 依赖注入)替代直接调用 LocalDateTime.now(),测试时注入固定时钟,使 "now" 返回固定值;或使用 Clock 库(如 Clocks)重写时间。时区处理:统一用 UTC 存储、应用层/展示层转本地时区,测试固定时区(ZoneId 注入),避免服务器时区差异导致断言失败。相对时间用"偏移固定基准"生成,如 fixedBase.plusDays(i),避免每次运行日期漂移。边界值(月初月末、闰年、夏令时切换、23:59:59)显式构造用例。数据库层可用 CURRENT_TIMESTAMP 映射到注入的时钟,或通过事务拦截器替换。

时间测试失稳的根源是不确定的时间源。把"当前时间"变成可注入、可固定的依赖,测试就从"依赖运行时"变成"确定性可复现",同时也规范了时区处理,避免生产环境时区问题。

// 注入固定时钟,使服务内 "now" 确定
Clock fixed = Clock.fixed(Instant.parse("2026-08-03T00:00:00Z"), ZoneId.of("UTC"));
OrderService svc = new OrderService(dep, fixed);
assertThat(svc.getCreatedAt()).isEqualTo(LocalDateTime.parse("2026-08-03T00:00:00"));
#
★★

14. 数据库连接池的可靠性测试,连接数上限、获取超时、连接泄漏与回收机制如何验证,池参数对并发的影响?

如何验证数据库连接池的连接数上限、获取超时、连接泄漏与回收机制?池参数对并发有何影响?

  • 连接池容量与超时行为的验证
  • 连接泄漏的检测与回收
  • 池参数对并发与吞吐的影响

连接池测试需验证:连接数上限——启动超过 maximumPoolSize 的并发请求,断言多余请求在 connectionTimeout 后抛出获取超时异常,且不阻塞数据库;连接获取超时——设置小的 connectionTimeout,断言超时后抛 SQLTransientConnectionException 并正确释放连接。连接泄漏与回收——用能检测泄漏的池(如 HikariCP 的 leakDetectionThreshold),在连接未归还时打印泄漏栈;或通过监控 active/idle 连接数与 getConnection 计数,断言事务结束后连接归池。池参数影响:maximumPoolSize 决定并发上限,过小会导致排队超时,过大可能打爆数据库;minimumIdle/idleTimeout/maxLifetime 影响连接复用与回收频率;connectionTimeout 决定等待上限。测试用固定并发数扫描不同池参数,对比吞吐、超时率、等待时间。

连接池是后端与数据库之间的"阀门",参数不当会引发连接耗尽或超时,是生产事故高发点。用并发压测 + 超时断言 + 泄漏检测机制,能同时验证容量行为、超时语义和回收机制,并量化参数对并发的影响。

#
★★

15. 字符集与排序规则测试,四字节字符、大小写与重音排序、不同 collation 下的查询与唯一约束行为如何覆盖?

如何覆盖四字节字符、大小写与重音排序、不同 collation 下的查询与唯一约束行为?

  • 四字节字符(utf8mb4)的存储与查询
  • 大小写/重音排序与 collation 语义
  • 唯一约束在 collation 下的行为

字符集测试需覆盖四字节字符:MySQL 的 utf8mb4 才能存 emoji 等四字节字符,测试应验证插入/查询/统计四字节字符不报错、不截断;VARCHAR 长度按字符还是字节计算需验证。collation 测试:utf8mb4_general_ci 忽略大小写、utf8mb4_bin 区分大小写、utf8mb4_unicode_ci 还按重音感知排序,需分别验证 ORDER BYWHERE =LIKE 对大小写和重音(如 é vs e)的处理。唯一约束在 collation 下:aA_ci 下视为重复、在 _bin 下视为不同,需验证插入冲突与否。建议测试在不同 collation 下运行同一组用例,断言排序与唯一约束行为符合预期,并单测写入/读取的编码一致性,避免乱码。

字符集与 collation 是"看似简单、实则隐蔽"的坑,会影响排序结果、唯一性判断和查询命中。测试的核心是"用一个明确约定的 collation 去断言行为",并覆盖四字节字符与大小写/重音这两类最容易出错的场景。

#

16. 时序数据库(InfluxDB/TimescaleDB)的测试特殊性,时间窗口聚合、降采样和数据保留策略如何验证?

时序数据库(InfluxDB/TimescaleDB)的测试有哪些特殊性?如何验证时间窗口聚合、降采样和数据保留策略?

  • 时间窗口聚合的正确性
  • 降采样(continuous aggregate / rollup)的验证
  • 数据保留策略(retention)的验证

时序数据库测试的特殊性在于"时间"与"数据量大"。时间窗口聚合验证:构造已知的时间序列数据,断言 GROUP BY time_bucket / 1h 的桶边界、空桶处理、聚合函数(mean/max/count)结果符合预期,尤其覆盖桶边界时刻与跨桶数据。降采样验证:TimescaleDB 用 continuous aggregate(连续聚合)、InfluxDB 用 downsampling(降采样),需验证降采样任务按调度刷新、聚合值与原始数据按窗口重算一致、刷新滞后(real-time aggregation)下最新数据的行为。数据保留策略验证:设置 retention policy,删除过期数据后断言旧数据被清理、新数据保留,且清理按分片/分段执行不阻塞写入。测试还需覆盖时间戳的时区、乱序写入、重复时间戳等边界。

时序数据库的"正确性"更多体现在时间窗口口径与数据量压力上。测试重点是把时间窗口、降采样、保留这三类"时间驱动的行为"变成可断言的事实,并验证在大量数据下仍保持正确与可查询。

#

17. 图数据库(Neo4j)的测试,图遍历查询的正确性和性能如何验证?

图数据库(Neo4j)的图遍历查询的正确性和性能如何验证?

  • 图遍历查询的结果正确性
  • Cypher 查询的路径与深度覆盖
  • 图查询性能的验证

图数据库测试需验证遍历查询的正确性:构造已知的图结构(节点、关系、权重),断言 Cypher 查询(如 MATCH (a)-[*1..3]->(b) 的可变长度路径、最短路径、连通分量)返回的节点/路径集合与手工推导一致,特别覆盖路径深度边界、无限环、无匹配、多路径等场景。关系方向与 labels 过滤也要验证。性能验证:用有人规模的图数据(百万级节点),测量遍历深度、结果集大小对耗时的影响,用 PROFILE/EXPLAIN 查看执行计划,确认是否使用索引(节点 label + 属性索引)、避免全图扫描;深度限制(*1..5)防止爆炸式遍历。还要验证图约束(节点 uniqueness、关系 cardinality)与索引。

图查询的复杂度在于路径与深度的组合爆炸,正确性测试要用"已知图 + 手工推导"来锚定结果,性能测试要控制深度并验证索引使用,避免图上无界遍历。

// 验证可变长度路径结果与深度上界
MATCH (a:Person {name:'Alice'})-[*1..2]->(b)
RETURN b.name ORDER BY b.name
#

18. 递归查询与窗口函数测试,CTE 递归深度限制、窗口函数分区排序的正确性与性能边界如何验证?

如何验证递归 CTE 的深度限制、窗口函数分区排序的正确性与性能边界?

  • 递归 CTE 的深度限制与终止条件
  • 窗口函数分区排序结果正确性
  • 常见性能边界

递归 CTE 测试:构造模拟树/组织架构数据,验证 WITH RECURSIVE 的锚点、递归步与终止条件,断言递归能正确遍历层级、在无环时收敛、在成环时被深度限制或去重截断;验证数据库递归深度上限(如 MySQL max_recursion_depth、PostgreSQL 递归 CTE 的检查)触发时的报错行为。窗口函数测试:构造已知数据,断言 ROW_NUMBER()/RANK()/DENSE_RANK() OVER (PARTITION BY ... ORDER BY ...) 的分区、排序、并列(tie)结果正确,尤其验证并列值、NULL 排序、分区边界。性能边界:大量数据下窗口函数的分区扫描与排序,验证是否利用索引、避免全量排序,测量递归深度与数据量增长对耗时的影响,设置合理上限。

递归与窗口函数是"结果易错、分支多"的 SQL 特性。递归测试锚定"终止与深度",窗口函数测试锚定"分区与并列语义",两者都用已知小数据手工推导结果,再在数据量放大时验证性能边界。

-- 验证 RANK 与 DENSE_RANK 在并列值下的差异
SELECT name, score,
       RANK() OVER (ORDER BY score DESC) AS r,
       DENSE_RANK() OVER (ORDER BY score DESC) AS dr
FROM students;