静态统计 SQL 时必须连 DELETE 一起看

新服务器上,数据初始化脚本第一次实跑,自检输出里有六项。

五项通过。唯一没过的那一项,恰好是我最看重的,站点配置表的条数。我预期 33 条,实跑出来 24 条。

复制代码
表数量                    33        期望 33        √
v2_site_config            24        期望 >=50      ×
v2_page_config            3                        √
v2_grade_config           6                        √
v2_site_nav               3                        √
v2_admin_user             1                        √

表数量 33 一次通过,这件事本身是个好消息。项目 SQL 头部写着 -- 引擎: PostgreSQL 15+。服务器上是 18.6-alpine,跨了 3 个大版本。我原本做好了「某个函数或扩展行为有差异」的心理准备,结果一条都没报错。

所以真正的问题只剩一个。24 到底对不对。

那 33 和 50 是怎么来的

脚本需要一个放行判据,我就去翻 SQL。项目一共 6 个脚本,我做了静态统计,数的是 INSERT。

数完发现,我此前在四份文档里写下的三个数字,全错了。

表总数我说 25 张,实际是 33 张。25 只是 init.sql 的数量。v2.4.0_admin.sql 另建 6 张,v2.6.0_marketing_page.sql 另建 2 张。

v2_site_config 我说 33 条,实际插入 56 条。init 32 条,seed 11 条,营销页脚本 4 条,另一个版本化增量脚本 9 条,UPSERT 去重后略少。

第三个数最离谱。我把一张配置表列成了检查项,那张表根本不存在。那个脚本只往 v2_site_config 做 UPSERT,再 UPDATE 一张导航表,它不建表。跑起来只会报错,说关系不存在。

前两个错误暴露的是同一个坏习惯。我在给 56 这个数拍脑袋打折。

56 个 INSERT 元组,UPSERT 会去重,我以为实际应该少一些。少多少,我没有依据。于是我凭感觉先拍了 33,后来又拍了「≥50」。

两个数字,两次都是猜的。而猜出来的东西,被我当成了判据。

后果很直接。如果我坚持 25 张表,一个完全正确的 33 会被我判成失败。而 ≥50 这个下限,会让一个完全正确的 24 永远无法放行。

追到 seed.sql 的开头

实跑结果摆在眼前,表数量 33 通过,配置表 24 没过。

我当时第一反应是,是不是哪个脚本没跑完。于是按顺序追了一遍,一直追到 seed.sql 的开头。

第 2 到第 9 行,是 8 条 DELETE。

sql 复制代码
DELETE FROM v2_product_reward_config;
DELETE FROM v2_product;
DELETE FROM v2_user_balance;
DELETE FROM v2_withdraw_account;
DELETE FROM v2_invite_relation;
DELETE FROM v2_user;
DELETE FROM v2_site_config;      ← init.sql 插的 32 条,在这里被清掉
DELETE FROM v2_site_nav;

我数 INSERT 的时候,眼睛是从 INSERT 这个词开始看的。这 8 行,我一个字都没看见。

它不是普通的种子脚本,它是清空重建

把执行链拼起来是这样。init.sql 往 v2_site_config 插入 32 条。seed.sql 先 DELETE 清空,再插入 11 条。v2.6.0_marketing_page.sql 加 4 条。另一个版本化增量脚本加 9 条。最终 24 条。

现在做一道小学减法。

sql 复制代码
静态统计的 INSERT 总数          56
减去被 DELETE 清掉的 init 部分  -32
──────────────────────────────────
实跑真值                        24

一减就对上了。24 从来不是需要被解释的异常值,它就是这套脚本唯一可能的答案。

留下的那句话是:静态统计 SQL 时,必须连 DELETE、TRUNCATE 一起看,否则算出来的预期值根本不成立。

看漏它的代价,不止是判据写错。这 8 条 DELETE 引出了这次部署最硬的一条红线。

seed.sql 会清空 8 张业务表,站点配置、站点导航、用户、商品都在里面。它是清空重建式的种子脚本,只允许在空库初始化时执行一次。在有真实数据的库上重跑,会直接删掉线上用户、商品与账户数据。

脚本里虽然有「库非空时需输入 YES」的二次确认,但那只是最后一道防线。更该记住的是这条铁律本身。

固化成判据

我把这次的教训压成一组动作,做 SQL 统计时照着做。

先数删操作,再数写操作。用 grep -nE '^\s*(DELETE|TRUNCATE|DROP)\b' *.sql 数一遍。不然得出的预期值会虚高。按脚本逐个统计,别只算总和。用 for f in *.sql; do echo -n " f";grep−cE ′\s ∗INSERT\s+INTO′"f "; grep -cE '^\s*INSERT\s+INTO' " f";grep−cE′\s∗INSERT\s+INTO′"f"; done,不然看不出是哪个脚本清的表。

UPSERT 不等于新增,带 ON CONFLICT 的语句会覆盖,条数不增,把它当成 56 就直接错了。检查项里的表名必须来自 CREATE TABLE。用 grep -hoE 'CREATE TABLE \^( *' *.sql | sort -u 反查一遍。不然会把不存在的表写进检查清单。

预期值必须由实跑校准,不能靠推算,做法是空库跑一遍取实测值。判据本身要写死,并且注明推导链,不然下次又有人顺手改一下。

我最后定稿的判据长这样,每条都带上了来源。

csharp 复制代码
表数量                    33        ← init 25 + admin 6 + 营销页 2
v2_site_config            24        ← seed DELETE 清空后重建:11 + 4 + 9
v2_page_config            >=3
v2_grade_config           >=6
v2_site_nav               >=3
v2_admin_user             >=1

还有一个统计命令,能把建表、写数据、清数据一次分开。

bash 复制代码
grep -nE '^\s*(CREATE TABLE|INSERT INTO|DELETE FROM|TRUNCATE|DROP TABLE)\b' *.sql

看这五类语句的相对位置,比看任何一类的数量都重要。

判据是我算出来的,而算就可能错。所以还需要一个不依赖我判断的机制,导入时带上 ON_ERROR_STOP。

bash 复制代码
psql -v ON_ERROR_STOP=1 -f init.sql

它的作用是,第一条报错的语句立刻停下并打印原文,而不是被静默跳过。没有它,一条失败的语句只会让最后的计数看起来不对,于是我又回到猜的阶段。

判据告诉你结果对不对,ON_ERROR_STOP=1 保证结果没有被偷偷改写。两者不是替代关系。

我给自己留的提醒只有一句。下次再算出某个预期值,先去数一遍 DELETE。

相关推荐
Nturmoils1 小时前
本来只想给内网开个口子,Natapp 却多给了一道防护
后端
WILLF1 小时前
《从0搭建AI聊天后端:FastAPI+OpenAI+SSE流式输出》
人工智能·后端
深圳磐链科技1 小时前
Web钱包开发需要哪些核心技术?解析多链集成、私钥管理与区块链交互方案
后端
精品源码屋2 小时前
Agent Skill 开发实战与工程指南
后端
雨中飘荡的记忆2 小时前
Claude Code 保姆级入门:从安装到跑通第一个项目
后端
茉莉玫瑰花茶2 小时前
GO [ 标准库 · unsafe ]
后端·go
肌肉娃子2 小时前
20261016 从定时任务到主明细入库:Spring Boot 对接亚马逊订单的数据同步设计
后端
Ysx2 小时前
比"本地全绿"更可怕的坑:"交付了"不等于"生效了"
java·后端
武子康2 小时前
ESP32-S3 分核后仍报 AFE 缓冲区满?先查输入合同
人工智能·后端·agent