SQLite 类型混用导致文章排序全乱:一个 Prisma + SQLite 的隐蔽 Bug
新文章永远排在最后一页,检查 publishedAt 日期完全正确,但排序就是不对。排查发现 SQLite 同一列混用了 text 和 integer 两种类型,导致排序结果完全不符合预期。
SQLite 类型混用导致文章排序全乱:一个 Prisma + SQLite 的隐蔽 Bug
现象
博客同时新增了两篇文章,publishedAt 设置为北京时间 2026年6月27日 晚上(UTC 2026-06-27T14:26 和 T16:00),按日期降序应该出现在首页最上方。但实际显示时,这两篇文章永远在最后一页(第四页),无论怎么修改日期都不行。
更诡异的是,更早的文章(6月27日下午)反而排在第一页最上面。
排查过程
第一步:确认数据库数据
直接查数据库,两篇文章的 publishedAt 分别是:
| slug | publishedAt |
|---|---|
| pm2-eaddrinuse-debugging | 2026-06-27T14:26:00.000Z |
| git-update-server-code-database | 2026-06-27T16:00:00.000Z |
日期完全正确,比首页第一篇(2026-06-27T07:26)更新。
第二步:检查 Prisma 查询
前端查询代码:
orderBy: { publishedAt: "desc" }
逻辑没问题。但用 Prisma Client 单独查时,发现返回顺序不对:
1. 2026-06-27T07:26:34 | nextjs-blog-deployment-ubuntu-nginx-ssl-pm2
...
74. 2026-06-28T15:44:00 | 1111
75. 2026-06-27T16:00:00 | git-update-server-code-database
76. 2026-06-27T14:26:00 | pm2-eaddrinuse-debugging
最新的三篇居然排在第 74-76 位!这肯定不是降序。
第三步:查 SQLite 原始存储类型
用 raw SQL 查存储类型:
SELECT slug, publishedAt, typeof(publishedAt) as type FROM Post ORDER BY publishedAt DESC;
结果:
| slug | publishedAt | 类型 |
|---|---|---|
| 旧文章(管理后台创建) | 2026-06-27T07:26:34.000Z | text |
| 新文章(脚本创建) | 2026-06-27T16:00:00.000Z | integer |
破案了。新旧文章的 publishedAt 存储了不同的 SQLite 数据类型。
旧文章(通过管理后台的 actions.ts 创建)存的是 text(ISO 8601 字符串)。
新文章(通过我写的 .cjs Prisma 脚本创建)存的是 integer(Unix 时间戳数字)。
根因分析
Prisma + SQLite 的 DateTime 行为
在 schema.prisma 中,publishedAt 定义为:
publishedAt DateTime?
Prisma 的 SQLite adapter 对 DateTime 字段的存储方式取决于传入值的类型:
- 传入 字符串(如
2026-06-27T14:26:00.000Z)→ 存为 text(ISO 字符串) - 传入 Date 对象(如
new Date(...))→ 存为 integer(Unix 毫秒时间戳)
我的创建脚本用的是:
publishedAt: new Date('2026-06-27T14:26:00.000Z')
而管理后台(actions.ts)用的是:
publishedAt: parsed.data.publishedAt // 从表单获取的 ISO 字符串
SQLite 的混合类型排序规则
SQLite 与 MySQL/PostgreSQL 不同,它不强制列的类型。同一列可以混存 text、integer、real 等多种类型。
关键在于 SQLite 的排序规则:SQLite 按类型分组排序。不同类型之间的大小关系是:
NULL < integer < real < text < blob
这就是说,integer 类型的值永远小于 text 类型的值,不管它们的数值大小如何。
所以 2026-06-27T16:00:00.000Z(integer)在排序时被认为小于 2026-06-27T07:26:34.000Z(text),即使前者日期更新。这就导致了新文章永远排在旧文章之后。
修复
用 raw SQL 把 integer 类型的 publishedAt 更新为 text 格式:
UPDATE Post SET publishedAt = '2026-06-27T14:26:00.000Z' WHERE slug = 'pm2-eaddrinuse-debugging';
UPDATE Post SET publishedAt = '2026-06-27T16:00:00.000Z' WHERE slug = 'git-update-server-code-database';
注意这里传的是字符串字面量而不是 Date 对象。Prisma 的 raw SQL 不会做类型转换,字符串保持 text 类型。
修复后验证:
1. git-update-server-code-database | 2026-06-27T16:00:00.000Z | text
2. pm2-eaddrinuse-debugging | 2026-06-27T14:26:00.000Z | text
3. nextjs-blog-deployment | 2026-06-27T07:26:34.000Z | text
排序正确,类型统一为 text。
预防措施
以后创建博客文章时,publishedAt 要用 字符串 而不是 Date 对象传入 Prisma:
// 错误:存成 integer
publishedAt: new Date('2026-06-27T14:26:00.000Z')
// 正确:存成 text(与现有数据一致)
// Prisma Client 修改时用 raw SQL 传字符串
await prisma.$executeRaw`UPDATE Post SET publishedAt = '2026-06-27T14:26:00.000Z' WHERE slug = '...'`
总结
这是一个典型的"数据库隐式类型"问题。Prisma 屏蔽了底层差异,但当同一列出现混合类型时,SQLite 的特殊排序规则就会暴露出来。
排查这类问题的关键:不要只看数据值对不对,要看数据的存储类型对不对。typeof() 是 SQLite 中极其有用的调试函数。
教训:Prisma + SQLite 的组合下,DateTime 字段必须统一传入方式。字符串和 Date 对象在 SQLite 中会产生不同的存储类型,进而影响排序、比较等操作的结果。