Mutation(状态变更操作)
Mutation 在应用开发中通常表示以改变 authoritative state 或触发业务副作用为目的的写操作/命令,与 Query/Read 相对;它不等同于某个 HTTP Method、数据库 UPDATE 或乐观更新。
[!info] related notes
- 所属 MOC:
- 前置概念:
- 并列概念:
- 易混淆概念:
- 关系笔记:
Mutation(状态变更操作)
一句话定义
在应用开发语境里,Mutation 通常不是中文日常语义里的“突然突变”,而是:
一个以改变系统状态或执行有业务副作用的命令为目的的操作。
中文理解成“状态变更操作”“写操作”“变更命令”通常比机械翻译成“突变”更自然。
最基本的对照是:
Query / Read
→ 读取、观察系统状态
Mutation / Write / Command
→ 改变系统状态或触发业务动作
为什么中文“突变”容易误导
英文 mutate 的基础语义就是 change / alter(改变、变更)。中文里“mutation”常因为生物学语境被翻成“突变”,容易让人产生“突然、剧烈变化”的联想,但软件工程里通常没有这个含义。
所以看到:
mutation
mutate state
useMutation
GraphQL Mutation
先翻译成:
变更
修改状态
写操作
状态变更操作
会更符合真实工程语义。
这个词在软件里还有另一种常见上下文
除了 API / Server State 的 Mutation,还经常看到:
mutate an object
mutate an array
state mutation
这里通常指直接改变一个已有对象当前保存的状态。
例如 JavaScript:
const items = [1, 2]
items.push(3)
push() 改变了原数组本身,因此可以说:
items was mutated
相比之下:
const nextItems = [...items, 3]
创建新数组而不改原数组,属于 immutable update 风格。
因此两个常见上下文可以这样区分:
Programming object mutation
→ 修改某个现有对象/数据结构的状态
API / Server-state Mutation
→ 发起一个以改变应用/服务器 authoritative state 为目的的操作
两者共同核心都是 change state,只是作用层级不同。
这也解释了 React 为什么常说“不要直接 mutate state”:那里讲的是对象/状态值的可变更新问题,不是 GraphQL/TanStack/Next.js 的网络 Mutation API。
最常见的 Mutation
CRUD 中:
Create → Mutation
Read → Query
Update → Mutation
Delete → Mutation
但 Mutation 的范围比数据库 CRUD 更大。例如:
createGoal
updateProfile
cancelRun
sendEmail
logout
approvePayment
markNotificationRead
archiveTask
publishArticle
startWorkflow
这些操作不一定都直接执行 SQL INSERT / UPDATE / DELETE,但它们的业务意图都是“让系统从一个状态转移到另一个状态”或“要求系统执行一个有副作用的命令”。
Mutation 不等于数据库 UPDATE
错误理解:
Mutation
= SQL UPDATE
更准确:
Mutation
= application / API semantic write operation
例如:
sendEmail
可能没有更新业务数据库,但会产生外部副作用;
cancelRun
可能更新状态机、写事件、终止 Worker;
logout
可能撤销 Session 或 Token。
所以 Mutation 更接近 Command / Use Case,而不是某条具体数据库语句。
Mutation 不等于 POST
HTTP Method 和 Mutation 是两个不同维度。
常见情况:
POST → 往往是 Mutation
PUT → Mutation
PATCH → Mutation
DELETE → Mutation
GET → 按 HTTP 语义应当是安全读取,不应以修改资源为主要目的
但不能简单写成:
Mutation === POST
因为有些 POST 可能只是复杂查询入口,而 Mutation 的核心判断是业务语义是否以改变状态为目的。
Mutation 与副作用的关系
Mutation 通常意味着业务副作用,例如:
状态改变
消息发送
任务入队
缓存失效
外部支付调用
文件写入
但不要反过来把“任何实现副作用”都叫 Mutation。
例如一个 Query 可能顺便:
写 access log
增加 metrics counter
记录 trace
它的公开业务语义仍然是 Query,因为调用者并不是为了改变业务状态而发起它。
因此判断 Mutation 应看:
operation contract / business intent,而不是函数内部是否碰巧产生了任何副作用。
Query 与 Mutation 的关键区别
Query
→ 我想知道现在是什么
Mutation
→ 我想让系统发生什么
例如 MemoFlow:
listGoals()
→ Query
createGoal()
→ Mutation
completeTask()
→ Mutation
BodySense:
getAssessment()
→ Query
updateAssessment()
→ Mutation
cancelRun()
→ Mutation
这个视角和 Command/Query Separation 很接近:读取与改变状态的意图应该尽量清晰分开。
Mutation 不等于非幂等
另一个常见误区:
Mutation
= 每调用一次都会产生不同结果
不一定。
幂等性回答:
同一个操作重复执行多次,最终状态是否和执行一次相同?
Mutation 回答:
这个操作是否以改变状态为业务目的?
因此:
PUT resource/123
→ Mutation
→ 可以设计成幂等
DELETE resource/123
→ Mutation
→ 通常也可以是幂等语义
POST /payments
→ Mutation
→ 往往不是天然幂等,需要 idempotency key
所以:
Mutation / Query
≠
Idempotent / Non-idempotent
是两条不同轴。
Mutation 在不同技术里的同一语义
GraphQL
mutation CreateGoal {
createGoal(...) { ... }
}
GraphQL 用 Mutation root type 显式表达写操作。详见 GraphQL Mutation 与错误语义。
TanStack Query
useMutation({
mutationFn: createGoal,
})
这里 Mutation 表示客户端发起的 server-state 写操作,并管理 pending / success / error、optimistic update、rollback 与 cache reconciliation。详见 Mutation 完整生命周期。
Next.js Server Actions
"use server"
export async function createGoal(...) { ... }
Server Action 通常作为 React UI 内部的服务端 Mutation 入口。详见 Next.js Server Actions。
三个技术 API 完全不同,但共享一个核心语义:
intent = change server/application state
Mutation 与 Optimistic Update 不是一回事
Mutation 是:
真正要求服务器执行状态变更
Optimistic Update 是:
服务器尚未确认 Mutation 成功
→ UI 先预测成功结果
→ 成功后 reconcile
→ 失败则 rollback
所以:
Mutation
= business operation
Optimistic Update
= mutation UX / client projection strategy
一个 Mutation 完全可以不用乐观更新;一个乐观更新必须最终与真实 Mutation 结果收敛。
Mutation 后为什么常要处理缓存
一旦 authoritative state 改变:
Old Cache
≠
New Truth
因此完整链路通常是:
Mutation
→ authoritative state changes
→ invalidate / patch cached projection
→ refetch / revalidate / reconcile
→ UI converges
不同框架只是 API 不同:
TanStack Query
→ invalidateQueries / setQueryData
Apollo
→ normalized cache update / refetch
Next.js
→ updateTag / revalidateTag / revalidatePath
底层问题都是同一个:写操作之后,旧 projection 怎么重新和 truth 一致。
最短记忆
Mutation
不要死记成“突变”
更适合记成:
状态变更操作 / 写操作 / Command
Query
= 告诉我现在是什么
Mutation
= 让系统发生某种变化