常见坑
每一章对着一个报错或一个现象:先用最小的程序把它复现出来(M2 Max、macOS、rustc 1.99.0、Postgres 16.14 上的原文),再到源码里找它从哪一行来,最后给出修法和检查办法;能在本站用的 1.1.20 上复现的,也贴出 1.1.20 的输出。机制在前面几部分讲过的只链过去。有几个坑的根子在 sqlx 或 sea-query,章里写明上游 issue;sqlx 本身的坑(池超时、unexpected null、cached plan 等)见 sqlx 卷的常见坑。
类型与解码
- Missing value for column 'body':2.0 的实体
Model把 NULL 落进非Option字段、select_only漏选的列、select_except排除的非空列,都报这一句;1.1.20 是两种不同的报错。 - incorrect binary data format in bind parameter 1:同一段 SQL 在同一条连接上先绑
i32再绑i64(反过来是08P01);sqlx 的语句缓存只按 SQL 文本做键,修法是一列只用一种 Rust 类型。 - column "level" is of type smallint but expression is of type "char":
i8字段建表是smallint,绑定和解码却是 sqlx 的"char",插入、过滤、读取全坏;用i16。 - column "status" is of type ticket_status but expression is of type text:
DeriveActiveEnum的db_type/enum_name和库里的 enum 对不上,读和写的表现不一样;库里多一个标签时整个all()失败。
查询
- contains("_") 匹配了每一行:
contains/starts_with/ends_with不转义%、_、\;先转义,再用like(LikeExpr::new(..).escape(..))。 - table name "staff" specified more than once:自引用或同一张表 join 两次时
join不起别名;用join_as或Linked。 - find_with_related(..).limit(n) 拿回的父记录不到 n 条:
LIMIT数的是 join 后的行,最后一个父记录的子记录被静默截断;先对父表分页再load_many。 - find_also_related 的右边全是 None:
from_query_result_optional默认.ok(),右侧实体的解码错误被吞掉,看起来像没有关联记录。 - Loader 报 too many arguments for query: 65536:Loader 用一条
IN取全部键、不分块,不同的键超过 65 535 个就超出 Postgres 的参数上限。
写入
- insert_many: null value in column "qty" violates not-null constraint:某一行设了、别的行没设的列,用带类型的 NULL 补而不是
DEFAULT,非空且有默认值的列报23502。 - None of the records are inserted:
Insert::exec冲突时是Err(RecordNotInserted),Conflicted只在TryInsert上;on_conflict_do_nothing()的冲突目标只是主键。 - None of the records are updated:
RecordNotUpdated只在至少一列Set、而WHERE没匹配到行时出现;什么都没Set时根本不发UPDATE。 - save 插进了一条新行,而不是更新:
save只看主键是不是NotSet,从表单或 JSON 来的ActiveModel没带主键就成了INSERT。 - after_save 返回了 Err,行却已经写进去了:
ActiveModelBehavior的钩子不开事务,after_save返回错误时那条语句已经提交。
连接与事务
- Failed to acquire connection from pool: Connection pool timed out:拿到的是池里另一条连接,看不到事务里未提交的写入;池小时等到
ConnectionAcquire(Timeout)。 - 超时取消 db.begin():连接带着事务回到池里:继承 sqlx 0.9.0 的问题,之后在这条连接上的写入悬空。
- connect_timeout 不起作用:连不上库时等的是 acquire_timeout:
connect_timeout和acquire_timeout写的是 sqlx 的同一个acquire_timeout,后写的总是赢。 - Connection Error: pool timed out while waiting for an open connection:值被原样拼进一条
SET语句,带引号或逗号后的空格都会出问题。
迁移
- duplicate key value violates unique constraint "pg_type_typname_nsp_index":同时启动的两个进程各自执行同一批迁移,其中一个撞上系统表的唯一索引(
IF NOT EXISTS也挡不住),不在事务里的数据迁移会执行两次;在Migrator::up外面加 advisory lock。 - 迁移中途失败:留下一半的结构,seaql_migrations 没有记录:不在事务里执行的迁移失败后,已执行的 DDL 留着,
seaql_migrations里却没有记录。
flowchart TB
q{"碰到了什么"}
q -->|"DbErr::Type"| t{"哪一句"}
t -->|"Missing value for column"| p1["1"]
t -->|"unexpected value for enum"| p4["4"]
q -->|"数据库返回错误"| db{"SQLSTATE"}
db -->|"22P03 或 08P01"| p2["2"]
db -->|"42804 或 42883"| p34["3 或 4"]
db -->|"42712"| p6["6"]
db -->|"23502"| p10["10"]
q -->|"没报错但结果不对"| sl{"哪种"}
sl -->|"搜索结果太多"| p5["5"]
sl -->|"关联记录少了"| p78["7 或 8"]
sl -->|"多出一行"| p13["13"]
sl -->|"写入不见了"| p1516["15 或 16"]
💬 关联讨论
发起讨论加载中...