跳到正文
Joeplover
工程·2026-06-28·约 4 分钟阅读

SQLite 类型混用导致文章排序全乱:一个 Prisma + SQLite 的隐蔽 Bug

新文章永远排在最后一页,检查 publishedAt 日期完全正确,但排序就是不对。排查发现 SQLite 同一列混用了 text 和 integer 两种类型,导致排序结果完全不符合预期。

SQLite database file icon with magnifying glass

SQLite 类型混用导致文章排序全乱:一个 Prisma + SQLite 的隐蔽 Bug

现象

博客同时新增了两篇文章,publishedAt 设置为北京时间 2026年6月27日 晚上(UTC 2026-06-27T14:26 和 T16:00),按日期降序应该出现在首页最上方。但实际显示时,这两篇文章永远在最后一页(第四页),无论怎么修改日期都不行。

更诡异的是,更早的文章(6月27日下午)反而排在第一页最上面。

排查过程

第一步:确认数据库数据

直接查数据库,两篇文章的 publishedAt 分别是:

slugpublishedAt
pm2-eaddrinuse-debugging2026-06-27T14:26:00.000Z
git-update-server-code-database2026-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;

结果:

slugpublishedAt类型
旧文章(管理后台创建)2026-06-27T07:26:34.000Ztext
新文章(脚本创建)2026-06-27T16:00:00.000Zinteger

破案了。新旧文章的 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 中会产生不同的存储类型,进而影响排序、比较等操作的结果。