Knex.js 命令速查表 - Knex SQL 查询构建器常用命令大全

面向想用 JavaScript 写 SQL、并能跨数据库迁移的 Node.js 开发者。Knex 的价值是可链式调用、自动参数绑定、不绑死某一种数据库;但要记得只有 .then/await 才会真正执行,以及何时用 raw() 逃生舱比继续构建表达式更清晰。读完能确定性地写出 CRUD、Join 与 groupBy 链,把多语句逻辑放进带回滚的事务,并用 debug/toString 查看生成的原生 SQL。

数据库·共 50 条命令·最后更新 2026-07-21
knexsqlquery buildernodejs

典型使用场景

面向想用 JavaScript 写 SQL、并且希望一套代码能跨 PostgreSQL / MySQL / SQLite 的开发者。Knex 的价值在于链式调用、自动参数绑定与跨库迁移能力;代价是各方言能力有差异(returning 只在 PG/SQLite 可用)。本表覆盖查询、写入、Join、聚合、事务、Schema 构建与原始查询七组,事务与防注入两节建议连着看。

查询数据 10

knex("users").select("*")
SELECT * FROM users
knex("users").select("name", "email")
查询指定列
knex("users").where("age", ">", 18)
WHERE age > 18
knex("users").where({ status: "active" })
WHERE status = active
knex("users").whereNull("deleted_at")
WHERE deleted_at IS NULL
knex("users").orderBy("created_at", "desc")
ORDER BY created_at DESC
knex("users").limit(10).offset(20)
LIMIT 10 OFFSET 20 分页
knex("users").first()
取第一条记录
knex("users").pluck("id")
取单列值数组 [1, 2, 3]
knex("users").distinct("country")
SELECT DISTINCT country

插入数据 5

knex("users").insert({ name: "John", email: "john@example.com" })
插入单条
knex("users").insert([{ name: "A" }, { name: "B" }])
批量插入
knex("users").insert(data).returning("id")
插入并返回 ID(PG/SQLite)
knex("users").insert(data).onConflict("email").merge()
冲突时更新(upsert)
knex("users").insert(data).onConflict("email").ignore()
冲突时忽略

更新与删除 5

knex("users").where("id", 1).update({ name: "Jane" })
更新指定记录
knex("users").where("id", 1).update({ views: knex.raw("views + 1") })
自增更新
knex("users").where("id", 1).update({ deleted_at: knex.fn.now() })
软删除模式
knex("users").where("id", 1).del()
删除指定记录
knex("users").truncate()
清空表(重置自增)

Join 关联 6

knex("users").join("posts", "users.id", "posts.user_id")
INNER JOIN
knex("users").leftJoin("posts", "users.id", "posts.user_id")
LEFT JOIN
knex("users").rightJoin("posts", "users.id", "posts.user_id")
RIGHT JOIN
knex("users").fullOuterJoin("posts", "users.id", "posts.user_id")
FULL OUTER JOIN
knex("users").crossJoin("posts")
CROSS JOIN 笛卡尔积
knex("users").join("posts", function() { this.on("users.id", "=", "posts.user_id").andOn("users.active", "=", knex.raw("?", [1])) })
多条件 JOIN

聚合与分组 8

knex("users").count("id as total")
COUNT(id) AS total
knex("users").count("* as total").first()
取总记录数
knex("orders").sum("amount")
SUM(amount)
knex("users").max("age")
MAX(age)
knex("users").min("age")
MIN(age)
knex("orders").avg("amount")
AVG(amount)
knex("users").groupBy("status")
GROUP BY status
knex("users").groupBy("status").having(knex.raw("count(*) > ?", [5]))
HAVING count > 5

事务 4

knex.transaction(async (trx) => { await trx("users").insert(data) })
自动提交/回滚事务
const trx = await knex.transaction(); await trx.commit() / trx.rollback()
手动控制事务
const sp = await trx.savepoint(async (sp) => { /* ... */ })
保存点(部分回滚)
await knex.transaction(cb, { isolationLevel: "read committed" })
设置事务隔离级别

Schema 构建 7

knex.schema.createTable("users", (t) => { t.increments("id"); t.string("name") })
创建表
knex.schema.dropTableIfExists("users")
删除表(如存在)
knex.schema.alterTable("users", (t) => { t.string("email") })
修改表结构
t.integer("age").unsigned().notNullable().defaultTo(0)
列约束链式调用
t.timestamps(true, true)
添加 created_at/updated_at
knex.schema.table("users", (t) => { t.index(["name", "status"]) })
创建复合索引
knex.schema.table("users", (t) => { t.unique("email") })
添加唯一约束

原始查询与调试 5

knex.raw("SELECT * FROM users WHERE id = ?", [1])
执行原生 SQL(参数化)
knex("users").where("active", true).debug(true)
输出查询 SQL 到控制台
knex("users").where("id", 1).toSQL().toNative()
查看生成的 SQL 和绑定值
knex("users").where("id", 1).toString()
返回 SQL 字符串
knex("users").columnInfo()
获取表结构信息

参数矩阵

参数作用示例
.where(col, op, val)条件查询,三参形式可写比较符knex('users').where('age', '>', 18)
.whereNull(col)生成 IS NULLknex('users').whereNull('deleted_at')
.onConflict(col).merge()upsert:冲突时更新已有行.onConflict('email').merge()
.returning(id)插入后返回主键(仅 PG / SQLite).insert(data).returning("id")
.del()删除匹配行knex('users').where('id', 1).del()
.truncate()清空整表并重置自增(不可回滚)knex('users').truncate()
knex.raw(sql, bindings)原生 SQL,? 为参数占位符knex.raw("SELECT * FROM users WHERE id = ?", [1])
.transaction(async (trx) => {})自动提交/回滚的事务包装await knex.transaction(async (trx) => { ... })
trx.savepoint(cb)保存点:内层失败只回滚该段const sp = await trx.savepoint(async (s) => { ... })
.having(knex.raw(...))分组后按聚合值过滤.having(knex.raw("count(*) > ?", [5]))
.debug(true)把生成的 SQL 打到控制台knex('users').debug(true)
.toSQL().toNative()查看 SQL 与绑定值,不执行knex("users").where("id",1).toSQL().toNative()

易错点与避坑指南

现象把用户输入拼进 knex.raw 后站点出现 SQL 注入风险。

原因字符串拼接绕过了 Knex 的参数绑定机制。

处置改用 knex.raw("... WHERE id = ?", [id]),或直接用链式 API。

现象事务执行了一部分后报错,之前的插入也没了。

原因事务回调内的查询忘记 await,事务在操作完成前就提交了。

处置确保回调内每个查询都 await;否则部分操作会跑在事务之外。

现象误用 truncate 清空了生产表且无法回滚。

原因truncate 是不可回滚的 DDL,且通常不接受 where。

处置生产环境改用 .where(...).del() 并先在小批量上验证。

现象upsert 后原有记录的其他字段被清空了。

原因.merge() 不指定列时会覆盖整行。

处置.merge(['name', 'updated_at']) 显式列出要更新的列。

现象同一段查询在 PostgreSQL 正常、在 MySQL 报错找不到 returning。

原因returning 是 PG / SQLite 特性,MySQL 不支持。

处置按方言分支处理,或改用 insert 后单独查询。

现象长连接闲置后首次查询报连接错误。

原因knex 实例被跨进程共享,连接已在别处失效。

处置每个进程各自初始化 knex 实例,不共享连接池对象。

排障路径

  1. 1先看生成的 SQL 对不对

    knex("users").where("id", 1).toSQL().toNative()

    不执行只打印 SQL 与绑定值,排查方言差异最快。

  2. 2确认字段名与表结构

    await knex.schema("users").columnInfo()

    字段拼错时 SQL 会在运行时报数据库错误,先核对结构。

  3. 3排查依赖版本冲突

    npm ls knex --depth=0

    确认装的是哪一版 knex 与哪一版数据库驱动。

  4. 4事务失败时定位是哪一步

    await knex.transaction(async (trx) => { await trx("a").insert(x); await trx("b").insert(y) })

    逐步注释找出失败的那条;配合保存点可只回滚其中一段。

  5. 5查看整棵依赖树

    npm why knex

    确认是否被某个上层依赖带入,避免多版本共存。

命令示例

用 ? 占位符防止 SQL 注入

knex('users').where('name', '?', name).toString() // 反例:knex.raw('SELECT * FROM users WHERE name = ' + name)

链式 API 自动绑定参数。确需原生 SQL 时用 knex.raw("... WHERE id = ?", [1]),绝不要把变量直接拼进语句。

输出

select * from `users` where `name` = 'tom'

调试:打印实际生成的 SQL

knex('users').where('active', true).toSQL().toNative()

需要看控制台输出可加 .debug(true)。生成结果与预期不符时,先看 SQL 再看数据,往往能立刻定位是方言差异还是逻辑问题。

输出

{
  sql: 'select * from `users` where `active` = ?',
  bindings: [ true ]
}

upsert:冲突时更新或忽略

await knex('users') .insert({ id: 1, name: 'ali' }) .onConflict('id') .merge()

onConflict("列名") 指定冲突键,后跟 .merge() 更新已有记录或 .ignore() 跳过。注意 .merge() 不加列表会覆盖整行,重要字段建议显式列出。

事务与保存点

await knex.transaction(async (trx) => { await trx('users').insert({ id: 1 }) const sp = await trx.savepoint(async (s) => { await s('orders').insert({ id: 1 }) }) })

保存点让内层失败只回滚该段而保留外层成果;没有它,一次失败会撤销整个事务的改动。回滚时抛出的错误即触发点。

常见坑与注意事项

  • knex.raw 拼接字符串等同于放开 SQL 注入,必须用 ? 占位符加 bindings 数组。
  • truncate 会清空整表并重置自增,是不可回滚的 DDL 且通常不带 where,不要在生产表上顺手执行。
  • .merge() 不指定列时会覆盖整行,可能清空未列出的字段;关键字段显式列出。
  • 事务回调内必须 await 所有查询;忘记 await 会导致事务在操作完成前就提交。
  • 同一连接池内不可跨进程共享 knex 实例,连接会失效;每进程各自初始化。

提示

  • Knex 支持 PostgreSQL、MySQL、MariaDB、SQLite、MSSQL,连接时指定 client。
  • 查询构建器返回 Promise,需要 await 或 .then() 才会执行。
  • knex.raw("SQL", bindings) 执行原生 SQL,参数绑定可防 SQL 注入。
  • Migration 用 knex.migrate.make() 创建,knex.migrate.latest() 执行迁移。
  • 连接池默认启用,配置 pool: { min: 2, max: 10 } 调整大小。

常见问题

Knex 怎么防止 SQL 注入?

不要自己拼接 SQL 字符串,用链式 API,Knex 会自动做参数绑定:knex("users").where("id", 1) 而不是 knex.raw("SELECT * FROM users WHERE id = " + id)。确实需要原生 SQL 时走 knex.raw("... WHERE id = ?", [1]),用 ? 占位符并把值放进数组,切勿把变量直接插进语句。链式调用上还可以加 .debug(true) 打印实际生成的 SQL 来复核。

truncate 和 del 有什么区别?

del() 只删除符合条件的行,表结构和自增序列保持不变;truncate() 清空整张表并重置自增主键,在多数数据库里底层是更高效的批量删除,但它是不可回滚的 DDL 操作且通常不能带 where 条件。清理测试数据常用 truncate,但它会一并删掉全部数据,别在生产库上顺手执行。

怎么调试一条 Knex 查询?

链上加 .debug(true) 会把生成的 SQL 打印到控制台,最直接。也可以用 .toSQL() 拿到 SQL 对象,.toSQL().toNative() 还能看到具体的驱动实现与绑定值,排查方言差异时很有用。columnInfo() 可以获取表结构,确认字段名是否写对。

保存点(savepoint)能解决什么问题?

事务里如果只有部分操作失败,需要回滚那一段而保留其余成果时用它。trx.savepoint(async (sp) => { ... }) 在其中失败只回滚到保存点,外层事务的其他修改仍然有效。没有嵌套保存点时,一次失败会把整个事务的改动全部撤销,这往往不是你想要的。

Knex 怎么支持多种数据库?

通过 knex 初始化时指定 client,比如 knex({ client: "pg" }) 或 "mysql2"、"sqlite3",同一套查询代码就能在 PostgreSQL、MySQL、SQLite 上跑。代价是各方言的能力有差异,比如 returning 只在 PostgreSQL 和 SQLite 可用,fullOuterJoin 也不是所有数据库都支持,写跨库代码时要留意。

官方参考来源

下方为命令对应的官方权威文档,供你核对最新用法与深入查阅。

由 巧匠 维护

公开更新于 2026年7月21日,内容持续校对官方文档。

联系我们

命令或描述有误?提交反馈、商务合作或产品建议都可发送邮件给我们。

联系我们