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