Rust in Everything
基于 sqlx 0.9.0

常见坑

每一章对着一个报错或一个现象:先用最小的程序把它复现出来(输出是 M2 Max、macOS、rustc 1.99.0、Postgres 16.14 上的原文),再到源码里找它从哪一行来,最后给出修法和检查办法。机制在前面几部分讲过的,这里只链过去,不再重复。有几个坑是 sqlx 自己的问题,章里写明在哪些版本上复现、上游 issue 是哪个。

  1. pool timed out while waiting for an open connection:手里占着一条连接(事务、没读完的 fetch 流)又拿 &pool 查询,池满时等的是自己;调大超时没用,事务里要传 &mut *tx。
  2. unexpected null; try decoding as an Option:NULL 读进了非 Option 的类型;query! 的可空性推断在空表上的 Right Join、盖在连接上面的 Limit 处推错,编译通过、运行报错,用 "col?" 修。
  3. mismatched types; Rust type i32 (as SQL type INT4) is not compatible with SQL type INT8:count(*) 是 INT8、NUMERIC 不能读 f64、TIMESTAMP 不能读 DateTime<Utc>;NULL 行跳过检查,错误到第一个非 NULL 行才出现。
  4. dynamic SQL strings should be audited for possible injections:0.9 起 query(&format!(...)) 编译不过;包一层 AssertSqlSafe 不算修好,值用 bind、标识符用白名单、动态结构用 QueryBuilder。
  5. set DATABASE_URL to use query macros online, or run cargo sqlx prepare to update the query cache:编译 query! 时既没连库也没有缓存;CI 用 SQLX_OFFLINE=true;.sqlx 过期时照样编译通过,要靠 prepare --check;0.9.0 会读上级目录的 .env。
  6. operator does not exist: bigint = bigint[]:IN ($1) 绑一个 Vec;Postgres 上写 = ANY($1),NOT IN 写 <> ALL($1)。
  7. too many arguments for query: 65536:一条语句最多绑 65535 个参数,超过时在发出任何消息之前报错;批量插入分批,或每列绑一个数组、用 UNNEST 展开。
  8. cached plan must not change result type:select * 进了语句缓存之后给表加列,这条连接上同一条 SQL 每次都报错、不会自己恢复;写明列名,或 DDL 之后 clear_cached_statements。
  9. 事务没 commit:函数返回 Ok,写入全部消失:Transaction 没 commit 就被 drop,Drop 只排进一条 ROLLBACK,不报错也不打日志。
  10. 事务里调 begin_with:外层事务被静默回滚:0.9.0 在已开的事务上调 begin_with,返回错误的同时回滚了外层事务,之后的语句逐条自动提交;0.8.6 没有这个问题。
  11. 超时取消 pool.begin():连接带着事务回到池里:BEGIN 已发出、回应未到时被取消,连接带着服务端事务回池,之后的写入全部悬空。
  12. migration 1 was previously applied but has been modified:改了已应用的迁移文件,校验和对不上,后面的新迁移一个也不跑;出错后 advisory lock 还留在池里的连接上。
  13. COPY from stdin failed: PgCopyIn dropped without calling finish() or fail():PgCopyIn 没 finish 也没 abort 就被 drop,之后这条连接上每个查询拿到的都是上一个查询的结果。
  14. unprintable error: (unprintable type):#[sqlx(try_from)] 转换失败时真实原因丢失;先解成原始类型再自己转。
  15. cache lookup failed for type N:自定义类型删掉重建,老连接还拿着旧 OID,直到连接被换掉。
  16. connect_lazy 设了 min_connections,连接数却一直是 0:维护任务只在关掉一条连接之后才补足 min_connections。
  17. sslmode=require 不校验服务端证书:require 和 prefer 给了根证书也不校验,和文档相反;防中间人要用 verify-full。
  18. sorry, too many clients already:几个池的 max_connections 加起来超过服务端上限;经连接池时只看到 PoolTimedOut。

按出在哪里分:

出在哪里坑
类型与解码:Decode、Type::compatible、可空性2 unexpected null、3 类型不兼容、14 unprintable
SQL 与参数:SqlSafeStr、bind、Parse4 SqlSafeStr、6 IN 列表、7 参数上限
编译期宏:数据源与缓存5 DATABASE_URL(2 的 query! 一半也在这里)
连接池与连接1 池超时、16 min_connections、17 sslmode、18 连接数
事务与迁移9 没提交、10 begin_with、11 取消 begin、12 迁移被改
驱动的边角:语句缓存、类型缓存、协议状态8 缓存的计划、13 COPY、15 类型 OID
flowchart TB
  q{"碰到了什么"}
  q -->|编译错误| ce{"哪一句"}
  ce -->|"audited for injections"| p4["4 SqlSafeStr"]
  ce -->|"DATABASE_URL"| p5["5 离线缓存"]
  q -->|运行时报错| rt{"哪一类"}
  rt -->|解码列| p23["2 或 3 或 14"]
  rt -->|"pool timed out"| p1["1 池超时"]
  rt -->|数据库返回| db{"服务端说了什么"}
  db -->|"operator does not exist"| p6["6 IN 列表"]
  db -->|"cached plan"| p8["8 缓存的计划"]
  db -->|"cache lookup failed"| p15["15 类型 OID"]
  db -->|"too many clients"| p18["18 连接数"]
  q -->|"没报错但结果不对"| sl{"哪种"}
  sl -->|数据没写进去| p9["9 或 10 或 11"]
  sl -->|"结果错位"| p13["13 COPY"]
  sl -->|"池里连接数不对"| p16["16 min_connections"]

💬 关联讨论

发起讨论
加载中...