你用 ORM 的原生查询搜了一批记录,拿到 id 列表,顺手拼成 in [...] 去做第二次查询。结果报错,说类型不对。明明打印出来就是数字啊。
现象
用原生查询拿到的结果,拼成一个 in [...] 查询时报错:
TypeError: Do not know how to serialize a BigInt
或者:
Cannot mix BigInt and other types, use explicit conversions
打印这些值的时候,console.log 显示的就是普通的数字,看不出任何异常:
const rows = await prisma.$queryRaw`select id from article where ...`;
console.log(rows); // [ { id: 1 }, { id: 2 }, { id: 3 } ] ← 看起来完全正常
这是这个坑最迷惑的地方:肉眼看不出问题,但类型就是不对。
还有一个常见的触发点:把原生查询的结果直接 JSON.stringify 返回给前端,或者交给日志库序列化——这些地方都会与 BigInt 冲突,而报错位置离真正的源头很远。
更坑的是错误的位置:报错往往不发生在"取数据"的那一行,而是发生在"用数据"的地方——比如对象序列化、算术运算、或者拼进查询参数时。于是你会盯着出错的那行看半天,而那行其实没问题,问题在更早的地方埋着。
根因
原生查询返回的整数,在客户端里是 BigInt,不是普通 number。
JavaScript 里 Number 是 IEEE 754 双精度浮点,安全整数范围只有 ±2^53-1。数据库的 BIGINT(64 位整数)超出这个范围时无法精确表示,所以驱动把这类整数值映射成了 BigInt 类型。
BigInt 和 number 是两种不同的类型,混用会报错:
typeof 1n // 'bigint'
typeof 1 // 'number'
1n + 1 // TypeError: Cannot mix BigInt and other types
JSON.stringify(1n) // TypeError: Do not know how to serialize a BigInt
为什么打印出来像数字? 因为 console.log 的显示形式里 1n 和 1 看起来几乎一样(有些环境下 BigInt 带个 n 后缀,有些格式化后完全看不出来)。
为什么只有原生查询出问题? 因为 ORM 对"托管查询"和"原生查询"的类型映射策略不同——通常模型字段上的 Int 会映射成 number(有模型元信息可以参考),但原生 SQL 查询(raw query)的结果没有模型元信息,驱动只能按数据库的实际类型来,于是变成 BigInt。
换句话说:托管查询有"模型的类型声明"这个上下文,驱动知道该往 number 转;原生查询没有这个上下文,驱动只能保守地按 BIGINT 处理。
解决
做法一:把取回的值转成 Number 再传入。
const rows = await prisma.$queryRaw`select id from article where ...`;
const ids = rows.map(r => Number(r.id)); // BigInt → number
await prisma.post.findMany({ where: { id: { in: ids } } });
做法二:如果值可能超出 Number 的安全范围,不要简单地 Number()(会丢精度),而是把比较条件也改成 BigInt,或者干脆在 SQL 层面用子查询 / join 完成,避免把值取回应用层再传回去:
// 用子查询,不经过应用层,天然没有类型转换问题
const rows = await prisma.$queryRaw`
select * from post
where "articleId" in (select id from article where ...)
`;
这是更根本的做法——当两边的查询都在数据库里时,类型根本不需要跨语言边界。
做法三:给 ORM 客户端配置类型映射,让它在原生查询里也返回 number。前提是业务上确认这些值不会超出安全整数范围(比如自增主键,实际用量远小于 2^53)。
延伸与预防
这条的通用教训:跨语言 / 跨驱动的"整数",类型未必是同一个。
具体建议:
一、在做 in 筛选、JSON 序列化、或者算术前,先想一下"这个值的类型到底是什么"。 用 typeof 打印,而不是用 console.log 肉眼判断:
console.log(typeof r.id, r.id); // 'bigint' 1n
二、遇到"打印出来挺正常的但就是报类型错",第一反应就怀疑 BigInt / 隐式类型转换。 这个症状组合很有辨识度。
三、项目里加一个小的工具函数统一处理这类转换,别在每个调用点各写一遍:
const toNum = v => (typeof v === 'bigint' ? Number(v) : v);
顺便记住一个相关的坑:JSON 序列化遇到 BigInt 会直接抛错,所以任何要往前端传的数据里如果有 BigInt,也必须先转换——否则接口会在返回时炸掉,而且错误堆栈可能指向序列化层,不指向你的业务代码。
再补一个容易忽略的连带影响:BigInt 不只影响传参,还会影响比较和排序。除了 in 之外,像 >、<、排序这类操作一旦混入不同类型的值,也可能出现意料之外的结果——要么抛错,要么比较行为不符合直觉。统一成同一种类型(全部 number,或全部 BigInt)是最省心的做法。