SQL存储过程实现幂等DDL的核心是将存在性检查与执行操作封装在同一事务内:SQL Server用sys.tables/sys.columns+EXEC动态执行;PostgreSQL优先to_regclass()和pg_attribute;MySQL需手动查information_schema并SELECT INTO变量,避免应用层“先查后建”引发竞态。

直接在存储过程中执行 CREATE TABLE 或 ALTER TABLE ADD COLUMN 会报错,不是语法写错了,而是数据库引擎主动拦住你——它不允许 DDL 破坏事务边界。要实现幂等,必须把“判断是否存在”和“执行操作”锁在同一事务里,靠应用层先查再建?那并发时必踩坑。
SQL Server:用系统视图 + IF NOT EXISTS 判断表/字段
SQL Server 不允许存储过程里直接写 CREATE TABLE,但允许在 IF 分支里动态拼接并用 EXEC 执行。关键不是能不能执行,而是判断逻辑必须走 sys.tables 和 sys.columns 这类元数据视图,且必须在同一个存储过程内完成:
-
IF NOT EXISTS (SELECT 1 FROM sys.tables WHERE name = 'logs')→ 判断表不存在才建 -
IF NOT EXISTS (SELECT 1 FROM sys.columns WHERE object_id = OBJECT_ID('logs') AND name = 'trace_id')→ 判断字段不存在才加 - 所有
EXEC动态语句必须放在IF块内部,不能提前拼好字符串再统一执行 - 注意
OBJECT_ID('logs')返回NULL时会导致整个子查询结果为空,所以必须用IS NULL显式判断
PostgreSQL:优先用 to_regclass(),别信 pg_tables
pg_tables 只查普通表,漏掉分区表、物化视图甚至临时表;to_regclass('orders') 才是正解——它返回 oid 或 NULL,类型安全且覆盖全:
IF to_regclass('events') IS NULL THEN EXECUTE 'CREATE TABLE events (...)' END IF;- 加字段时别用
information_schema.columns,它慢且受 search_path 影响;改用SELECT 1 FROM pg_attribute WHERE attrelid = 'events'::regclass AND attname = 'status' - DO 块适合初始化(如建库建用户),但无法被其他存储过程调用;真要复用,得写成
FUNCTION并设SECURITY DEFINER - PostgreSQL 的
EXECUTE必须拼字符串,参数要用format()转义,否则 SQL 注入风险拉满
MySQL:5.7+ 支持 IF NOT EXISTS,但别全信它
CREATE TABLE IF NOT EXISTS 是个甜点,但它只认表名,不校验字段、索引、字符集是否一致。一旦结构变更,重复执行就静默失败:
- 加字段必须手动查
information_schema.COLUMNS:SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'metrics' AND COLUMN_NAME = 'unit' - MySQL 存储过程里不能直接用
IF NOT EXISTS (SELECT ...)当条件,得先SELECT INTO @exists,再用IF @exists = 0 -
ALTER TABLE ... ADD COLUMN IF NOT EXISTS是 MySQL 8.0.23+ 才支持的,低版本必须自己查元数据 - 注意
DATABASE()返回当前默认库名,跨库操作时得显式指定TABLE_SCHEMA
最易被忽略的一点:所有判断逻辑必须在存储过程体内完成,哪怕只是多一层应用层的“先查后调”,都会在高并发下让幂等失效——因为两次查询之间存在时间窗口,别人可能已经建好了表,而你的存储过程还在路上。

















