Rust in Everything
基于 sea-orm 2.0.4

常见坑

每一章对着一个报错或一个现象:先用最小的程序把它复现出来(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 卷的常见坑。

类型与解码

  1. Missing value for column 'body':2.0 的实体 Model 把 NULL 落进非 Option 字段、select_only 漏选的列、select_except 排除的非空列,都报这一句;1.1.20 是两种不同的报错。
  2. incorrect binary data format in bind parameter 1:同一段 SQL 在同一条连接上先绑 i32 再绑 i64(反过来是 08P01);sqlx 的语句缓存只按 SQL 文本做键,修法是一列只用一种 Rust 类型。
  3. column "level" is of type smallint but expression is of type "char":i8 字段建表是 smallint,绑定和解码却是 sqlx 的 "char",插入、过滤、读取全坏;用 i16。
  4. column "status" is of type ticket_status but expression is of type text:DeriveActiveEnum 的 db_type / enum_name 和库里的 enum 对不上,读和写的表现不一样;库里多一个标签时整个 all() 失败。

查询

  1. contains("_") 匹配了每一行:contains / starts_with / ends_with 不转义 %、_、\;先转义,再用 like(LikeExpr::new(..).escape(..))。
  2. table name "staff" specified more than once:自引用或同一张表 join 两次时 join 不起别名;用 join_as 或 Linked。
  3. find_with_related(..).limit(n) 拿回的父记录不到 n 条:LIMIT 数的是 join 后的行,最后一个父记录的子记录被静默截断;先对父表分页再 load_many。
  4. find_also_related 的右边全是 None:from_query_result_optional 默认 .ok(),右侧实体的解码错误被吞掉,看起来像没有关联记录。
  5. Loader 报 too many arguments for query: 65536:Loader 用一条 IN 取全部键、不分块,不同的键超过 65 535 个就超出 Postgres 的参数上限。

写入

  1. insert_many: null value in column "qty" violates not-null constraint:某一行设了、别的行没设的列,用带类型的 NULL 补而不是 DEFAULT,非空且有默认值的列报 23502。
  2. None of the records are inserted:Insert::exec 冲突时是 Err(RecordNotInserted),Conflicted 只在 TryInsert 上;on_conflict_do_nothing() 的冲突目标只是主键。
  3. None of the records are updated:RecordNotUpdated 只在至少一列 Set、而 WHERE 没匹配到行时出现;什么都没 Set 时根本不发 UPDATE。
  4. save 插进了一条新行,而不是更新:save 只看主键是不是 NotSet,从表单或 JSON 来的 ActiveModel 没带主键就成了 INSERT。
  5. after_save 返回了 Err,行却已经写进去了:ActiveModelBehavior 的钩子不开事务,after_save 返回错误时那条语句已经提交。

连接与事务

  1. Failed to acquire connection from pool: Connection pool timed out:拿到的是池里另一条连接,看不到事务里未提交的写入;池小时等到 ConnectionAcquire(Timeout)。
  2. 超时取消 db.begin():连接带着事务回到池里:继承 sqlx 0.9.0 的问题,之后在这条连接上的写入悬空。
  3. connect_timeout 不起作用:连不上库时等的是 acquire_timeout:connect_timeout 和 acquire_timeout 写的是 sqlx 的同一个 acquire_timeout,后写的总是赢。
  4. Connection Error: pool timed out while waiting for an open connection:值被原样拼进一条 SET 语句,带引号或逗号后的空格都会出问题。

迁移

  1. duplicate key value violates unique constraint "pg_type_typname_nsp_index":同时启动的两个进程各自执行同一批迁移,其中一个撞上系统表的唯一索引(IF NOT EXISTS 也挡不住),不在事务里的数据迁移会执行两次;在 Migrator::up 外面加 advisory lock。
  2. 迁移中途失败:留下一半的结构,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"]

💬 关联讨论

发起讨论
加载中...