更新和删除查询
本指南涵盖数据库查询构建器上的 UPDATE 和 DELETE 方法。你将学习如何:
- 更新匹配行中的一列或多列
- 原子性地递增和递减数值列
- 删除行
- 使用
returning读回受影响的行 - 在事务内运行更新和删除
- 理解只读安全防护
概述
数据库查询构建器公开了 update、delete(别名 del)、increment 和 decrement 用于修改行。这些方法位于与 select 相同的构建器上,因此你以完全相同的方式组合 WHERE、JOIN 和 ORDER 子句,并以修改调用结束链。
import db from '@adonisjs/lucid/services/db'
await db.from('users').where('id', 1).update({ is_active: false })
对于插入操作,请参阅 insert 查询构建器。对于 SELECT 以及用于限定这些查询作用域的完整 WHERE 和 JOIN 辅助方法列表,请参阅 select 查询构建器。对于原始 SQL,请参阅 raw 查询构建器。
update、delete、increment 和 decrement 的返回值默认是受影响的行数。当设置了 returning 时,结果是返回行的数组。
更新行
update
传入列值对象以同时更新多列。
await db
.from('users')
.where('id', 1)
.update({
is_active: false,
deactivated_at: new Date(),
})
传入两个参数以更新单列。
await db
.from('users')
.where('id', 1)
.update('email', 'new@adonisjs.com')
使用 db.raw 作为值,根据其现有内容或 SQL 表达式更新列。
await db
.from('posts')
.where('id', params.id)
.update({
view_count: db.raw('view_count + 1'),
last_viewed_at: db.raw('NOW()'),
})
查询返回受影响的行数。
const affected = await db
.from('users')
.where('is_active', false)
.update({ status: 'archived' })
// affected === 受影响的行数
始终在更新查询中包含 where 子句。如果没有它,表中的每一行都会被更新。Lucid 不要求 where 子句来保证安全;这个责任在于调用者。
返回更新的列
在支持 RETURNING 的方言上(PostgreSQL、MSSQL、Oracle、SQLite 3.35+),调用 returning 从受影响的行读回列。结果变成对象数组而不是受影响行数。
const updated = await db
.from('users')
.where('plan', 'free')
.update({ plan: 'pro' })
.returning(['id', 'email'])
updated.forEach((user) => console.log(user.id, user.email))
returning 接受单个列、列数组或 '*' 表示所有列。在 MySQL 上,returning 被静默忽略,查询仍然解析为受影响行数。
递增和递减
increment
原子性地增加数值列。没有计数器参数时,列递增 1。
await db
.from('posts')
.where('id', params.id)
.increment('view_count')
await db
.from('posts')
.where('id', params.id)
.increment('view_count', 5)
decrement
原子性地减少数值列。没有计数器参数时,列递减 1。
await db
.from('inventory')
.where('product_id', productId)
.decrement('stock')
await db
.from('inventory')
.where('product_id', productId)
.decrement('stock', quantity)
递增和递减在数据库端作为 UPDATE column = column +/- value 发出,因此它们在并发写入下是安全的,无需显式事务或行锁。
删除行
delete
针对匹配当前链的行发出 DELETE 语句。
await db
.from('sessions')
.where('user_id', user.id)
.delete()
查询返回受影响的行数。
const removed = await db
.from('expired_tokens')
.where('expires_at', '<', new Date())
.delete()
console.log(`Removed ${removed} expired tokens`)
del 是 delete 的别名,行为完全相同。使用在你的代码库中读起来更好的那个。
await db.from('sessions').where('id', sessionId).del()
始终在删除查询中包含 where 子句。如果没有它,表中的每一行都会被删除。
返回删除的列
在支持 RETURNING 的方言上,调用 returning 从删除的行读回列。当你想要记录删除的行或将它们传递给应用的另一部分时很有用。
const removed = await db
.from('comments')
.where('post_id', params.id)
.delete()
.returning(['id', 'author_id'])
await audit.recordDeletion('comment', removed)
在事务内部
要在事务内更新或删除行,直接在事务客户端上构建查询。trx 对象暴露与 db 服务相同的 .from 入口点。完整模式请参阅 事务指南。
await db.transaction(async (trx) => {
await trx
.from('orders')
.where('id', orderId)
.update({ status: 'paid', paid_at: new Date() })
await trx
.from('inventory')
.where('product_id', productId)
.decrement('stock')
})
两个修改要么一起提交,要么如果任一查询抛出异常则一起回滚。
只读模式保护
当你在 read 模式下获取查询客户端时(通常通过调用 db.connection('postgres', { mode: 'read' })),构建器拒绝执行 update、delete、increment 或 decrement 并抛出异常:
Updates and deletes cannot be performed in read mode
这个防护防止意外写入只读副本。要执行写入,使用默认(dual)或显式 write 模式。
// 安全:dual 模式自动将写入路由到主节点
await db.from('users').where('id', 1).update({ last_seen_at: new Date() })
// 显式写入模式
await db
.connection('postgres', { mode: 'write' })
.from('users')
.where('id', 1)
.update({ last_seen_at: new Date() })
检查查询
select 查询构建器 中记录的相同检查辅助方法适用于更新和删除查询。
const { sql, bindings } = db
.from('users')
.where('id', 1)
.update({ is_active: false })
.toSQL()
使用 debug(true) 在 db:query 事件上发出此查询而不启用连接级调试,使用 timeout(ms, { cancel: true }) 中止长时间运行的修改。
db
.from('analytics_events')
.where('created_at', '<', cutoff)
.delete()
.timeout(30_000, { cancel: true })
完整调试工作流请参阅 调试指南。