首页 / 文章 / 用AI构建数据库性能测试工具:诚实复盘
← 返回
AI技术

用AI构建数据库性能测试工具:诚实复盘

✍️ zhirenhun 📅 2026/5/28 👁 55 阅读 ⏱ 17 分钟
用AI构建数据库性能测试工具:诚实复盘

让 AI 来写几乎所有代码还是感觉有点奇怪——但我决定在这个新项目上认真试一试。背景是:我快没什么项目点子可分享了,于是让 AI 列了一个清单,选了其中一个叫数据库性能测试(Database Performance Testing)的项目。目标是对关系型数据库运行性能测试。我用大约两天时间完成,在这篇文章中,我想分享我的真实感受——既有技术层面的,也有关于 AI 辅助开发流程本身的。


为什么要对数据库做性能测试?

我认为这是开始任何项目之前需要问的第一个问题。碰巧的是,我目前参与的一个项目中,数据性能是系统的关键关注点——这让我开始思考:直接对关系型数据库运行性能测试会是什么样子?

从 QA 角度看,性能不只是 API 如何连接数据库的问题。它还与查询是如何编写的以及数据库如何处理查询并发有关,特别是在同步系统中。一个缓慢的端点并不总是端点的锅——有时是背后隐藏着慢查询。这就是驱动我从此处开始的原因。


我们在测试什么?

这个项目有四个不同的测试场景:

N+1 查询检测

N+1 是访问数据库的应用中一个经典的性能问题——尤其是使用 ORM(如 Entity Framework、SQLAlchemy 或 Sequelize)的系统。这个名字精确地描述了问题:应用程序最终运行的不是一次优化后的查询,而是1条初始查询 + N条额外查询

考虑一个列出 20 个订单并显示每个订单用户邮箱的电商系统。朴素的做法是:

SELECT id, user_id FROM orders LIMIT :n;
SELECT email FROM users WHERE id = :uid;

第一条查询获取订单。第二条查询随后运行了 20 次——每个订单一次。这就是你的 N+1。大规模下的后果包括:延迟成倍增加、缓存污染、以及用户不可预测的响应时间。

在并发场景中也会出现。两个线程同时拉取用户数据,每个都触发自己的级联查找,会迅速成倍增加数据库负载,而且没有工具很难追踪。


死锁模拟

想象两个同时进行的事务:

事务 A:

UPDATE orders SET status = 'pending' WHERE id = 1;
UPDATE orders SET status = 'paid'    WHERE id = 2;

事务 B(同时运行,顺序相反):

UPDATE orders SET status = 'paid'    WHERE id = 2;
UPDATE orders SET status = 'pending' WHERE id = 1;

每个事务持有对方需要的锁。数据库检测到循环并通过回滚其中一个事务来解决——但这种回滚是有代价的。在大规模下,这类争用会导致明显的延迟尖峰,最坏情况下会锁住应用程序的部分功能。死锁测试的目标不仅是确认死锁可以发生,更是要衡量数据库如何恢复以及时间影响有多大。


Schema 变更后的查询回归追踪

这个场景专注于度量 Schema 变更——索引创建、迁移、表修改——对查询执行计划和耗时的影响。它在迁移前后捕获 EXPLAIN ANALYZE 输出,然后对比差异。

特别适用于:


慢查询

慢查询测试充当一个延迟门禁(latency gate)——一组关键查询必须保持在定义的门限(在 config.py 中配置为 SLOW_QUERY_THRESHOLD_MS)之下。如果任何查询在 CI 中突破了门限,流水线会失败。

可以把它理解为最重要数据库操作的性能预算。本项目中监控了五个关键查询。以下是低负载基准运行的结果:

查询平均 (ms)p95 (ms)最大 (ms)
`user_lookup`0.200.290.35
`order_history`0.310.430.75
`inventory_search`0.240.320.62

三个查询都轻松通过了门限。但这里的价值不只是绿灯——而是拥有一个历史基线。下次有人添加筛选条件、修改 JOIN 或删除索引时,你会立刻知道这些数字是否发生了变化。


解决方案结构

项目结构如下:

.
├── config.py                          # DB_URL 和 SLOW_QUERY_THRESHOLD_MS
├── conftest.py                        # pytest fixtures: engine, instrumented_engine
├── pyproject.toml                     # pytest 配置和标记
├── requirements.txt
├── analysis/
│   ├── n_plus_one_detector.py         # N+1 检测和模拟
│   ├── deadlock_simulator.py          # 并发死锁演示
│   └── explain_analyzer.py            # EXPLAIN 计划捕获和差异比较
├── benchmarks/
│   ├── queries/                       # 原始 .sql 文件(12个查询)
│   ├── scenarios/
│   │   └── run_benchmark.py           # 容量基准运行器
│   ├── test_n_plus_one.py             # N+1 检测测试
│   ├── test_deadlock.py               # 死锁测试
│   ├── test_explain.py                # EXPLAIN ANALYZE 计划测试
│   └── test_slow_queries.py           # 延迟门限测试(5个关键查询)
├── data/
│   ├── seed.py
│   └── distributions.json
├── migrations/
│   ├── baseline/
│   │   └── 001_initial_schema.sql
│   └── v2_add_indexes/
│       └── 002_add_indexes.sql        # 回归演示的示例迁移
├── reports/
│   ├── query_regression_report.py     # 差异报告器(也可导出到 Grafana)
│   ├── export_metrics.py              # 将结果写入 benchmark_results 表
│   ├── output/                        # 带时间戳的基准 JSON 结果
│   └── plans/                         # 保存的 EXPLAIN 计划
├── scripts/
│   └── setup_schema.py                # 应用迁移 + 快照 schema
├── .github/
│   └── workflows/
│       └── performance-tests.yml      # CI: schema → seed → pytest
└── docker/
    ├── docker-compose.yml             # PostgreSQL + Grafana
    ├── init.sql
    └── grafana/
        ├── provisioning/
        └── dashboards/benchmark.json  # 自动配置的仪表盘

这个结构中值得强调的几点:

analysis/ 设计为可独立运行。 每个模块——N+1 检测、死锁模拟、EXPLAIN 分析——都可以单独执行。你不必每次都运行全套。这在实际中有意义:有时你只关心迁移后的回归追踪,同时运行死锁模拟只是噪音。

explain_analyzer.py 捕获和比较 EXPLAIN 计划。 这是查询回归场景的核心——它在 Schema 变更前后保存计划快照并计算差异。结果以带时间戳的 JSON 文件存储在 reports/plans/ 下。

Grafana 自动配置。 export_metrics.py 在每次运行后将数据写入 benchmark_results 表,grafana/dashboards/benchmark.json 中的仪表盘自动加载。无需手动设置。


数据实际显示的结果

最有趣的结果来自查询回归场景。通过迁移 002_add_indexes.sql 添加复合索引(idx_orders_user_created)后,order_history 的 EXPLAIN 计划发生了巨大变化:

索引前——顺序扫描(Sequential Scan):

索引后——位图索引扫描(Bitmap Index Scan):

执行时间减少了 83%,规划器成本下降了 ~10 倍——从扫描 2500 行的全表扫描到精准的索引查找。如果没有回归追踪,这种变化只有在有人碰巧手动运行 EXPLAIN 时才会被发现。

这正是值得放在 CI 中的验证方式:添加索引,运行测试套件,得到清晰的 before/after 差异,确认优化确实有效。


关于用 AI 构建的诚实评价

这部分我想多花点时间聊聊,因为 AI 辅助的工作流和最终的代码产出一样值得反思。

我实际做的是:让 AI 提出项目点子、细化范围,然后迭代地让它生成解决方案——先结构、再各个模块、然后 CI 流水线。我没有从头写太多代码。我真正花时间的是阅读 AI 生成的代码,质疑测试逻辑是否合理,验证场景是否对应了它们要模拟的真实问题。

这个工作流与我自己全部手写的不同之处:

AI 真正帮上忙的地方: 搭建 Docker + Grafana 集成这件事如果我自己手动做要花半天。AI 在几分钟内就有了一个自动配置仪表盘的可用 docker-compose.yml。仅此一项,对于 POC 来说就值得了。

AI 不足的地方: AI 容易产出看起来很自信但需要仔细审查的代码。有几个生成的 SQL 文件的列名与 Schema 不匹配。N+1 检测逻辑需要额外处理才能正确处理并发情况。这些都不是阻塞性问题,但它们强化了一个事实:人机协作不是可选项——它就是工作本身。

我会继续尝试 AI 辅助的工作流,特别是测试工具和 POC 方面。如果你构建过类似的东西,或者对这个方法有想法,欢迎在评论区交流。完整解决方案可以在这里查看。

——

🧑‍💻

zhirenhun

一个热爱技术的程序员,喜欢分享前沿AI知识和开发经验。

ai testing database performance
← 上一篇
手动内存管理的价值:为何在GC时代仍需掌控内存
下一篇 →
从零构建容器:50行代码实现容器运行时(第2部分)

📌 相关推荐

走向 Agent 记忆的标准模型
2026/5/31
浏览器内部的悄然 AI 战争
2026/5/31
为什么 AI 会忘记你说过的话(以及如何解决)
2026/5/31
← 返回文章列表