更新和删除查询

更新和删除查询

本指南涵盖数据库查询构建器上的 UPDATE 和 DELETE 方法。你将学习如何:

  • 更新匹配行中的一列或多列
  • 原子性地递增和递减数值列
  • 删除行
  • 使用 returning 读回受影响的行
  • 在事务内运行更新和删除
  • 理解只读安全防护

概述

数据库查询构建器公开了 updatedelete(别名 del)、incrementdecrement 用于修改行。这些方法位于与 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 查询构建器

updatedeleteincrementdecrement 的返回值默认是受影响的行数。当设置了 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`)

deldelete 的别名,行为完全相同。使用在你的代码库中读起来更好的那个。

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' })),构建器拒绝执行 updatedeleteincrementdecrement 并抛出异常:

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 })

完整调试工作流请参阅 调试指南