None of the records are updated
主线位置:写入主线的 update 分支。
am.update(&db)用主键做WHERE、用Set的非主键列做SET,Postgres 上带RETURNING一条语句拿回整行;没有返回行就是这个错误。三种执行路径(有列要写、没有列要写、没匹配到行)的完整机制见 update 与 delete,这一页只讲什么时候会看到它、什么时候看不到。
flowchart TB
am["am.update"] --> setc{"有非主键列是 Set"}
setc -->|有| upd["UPDATE SET WHERE pk RETURNING"]
upd -->|0 行| e1["RecordNotUpdated"]
upd -->|1 行| m1["Model"]
setc -->|没有| sel["只按主键 SELECT"]
sel -->|0 行| e2["RecordNotFound"]
sel -->|1 行| m2["Model,什么都没写"]
本页实验在 M2 Max、macOS、rustc 1.99.0、Postgres 16.14 上运行,sea-orm 2.0.4,以及本站用的 1.1.20,同一份代码。表 item 有三行,id 为 1、2、3。SQL 取自 sqlx 的语句日志(sqlx=info)。
现象
None of the records are updated
这是 DbErr::RecordNotUpdated 的 Display(error.rs:#[error("None of the records are updated")]),不带表名、不带主键。它只说明 UPDATE 的 WHERE 没有匹配到行。
最小复现
src/bin/not_updated.rs,ghost() 是一个内存里造的 id = 99 的 Model:
// 1. one column Set, id 99 does not exist
let mut am = ghost().into_active_model();
am.qty = Set(1);
am.update(&db).await;
// 2. read c3, Set the primary key to 30 and change name
let mut am = c3.into_active_model();
am.id = Set(30);
am.name = Set("cherry v2".into());
am.update(&db).await;
// 3. nothing Set, id 99 does not exist
ghost().into_active_model().update(&db).await;
// 4. nothing Set, id 2 exists
b2.into_active_model().update(&db).await;
2.0.4 的输出(第 2、4 段读行的 SELECT 删掉了,标为 ...):
--- 1. one column Set, id 99 does not exist
summary="UPDATE \"item\" SET \"qty\" …" db.statement="\n\nUPDATE \"item\" SET \"qty\" = $1 WHERE \"item\".\"id\" = $2 RETURNING \"id\", \"sku\", \"name\", \"qty\", \"note\"\n" rows_affected=0 rows_returned=0 elapsed=1.657417ms elapsed_secs=0.001657417
=> Err RecordNotUpdated
Display: None of the records are updated
--- 2. read c3, Set the primary key to 30 and change name
...
summary="UPDATE \"item\" SET \"name\" …" db.statement="\n\nUPDATE \"item\" SET \"name\" = $1 WHERE \"item\".\"id\" = $2 RETURNING \"id\", \"sku\", \"name\", \"qty\", \"note\"\n" rows_affected=0 rows_returned=0 elapsed=348.291µs elapsed_secs=0.000348291
=> Err RecordNotUpdated
Display: None of the records are updated
--- 3. nothing Set, id 99 does not exist
summary="SELECT \"item\".\"id\", \"item\".\"sku\", \"item\".\"name\", …" db.statement="\n\nSELECT \"item\".\"id\", \"item\".\"sku\", \"item\".\"name\", \"item\".\"qty\", \"item\".\"note\" FROM \"item\" WHERE \"item\".\"id\" = $1 LIMIT $2\n" rows_affected=0 rows_returned=0 elapsed=174.833µs elapsed_secs=0.000174833
=> Err RecordNotFound("Failed to find updated item")
Display: RecordNotFound Error: Failed to find updated item
--- 4. nothing Set, id 2 exists
...
summary="SELECT \"item\".\"id\", \"item\".\"sku\", \"item\".\"name\", …" db.statement="\n\nSELECT \"item\".\"id\", \"item\".\"sku\", \"item\".\"name\", \"item\".\"qty\", \"item\".\"note\" FROM \"item\" WHERE \"item\".\"id\" = $1 LIMIT $2\n" rows_affected=1 rows_returned=1 elapsed=136.042µs elapsed_secs=0.000136042
=> Ok Model { id: 2, sku: "b2", name: "banana", qty: 5, note: None }
1.1.20 上四段的语句和结果逐条相同。
| 情况 | 发出的语句 | 结果 |
|---|---|---|
至少一列 Set,主键对应的行不存在(被删了、id 写错了) | UPDATE ... RETURNING,0 行 | RecordNotUpdated |
读出来的行把主键 Set 成新值,再改别的列 | UPDATE ... SET "name" = $1 WHERE "id" = $2,$2 是新值 30 | RecordNotUpdated |
一列都没 Set,行不存在 | 只有 SELECT | RecordNotFound("Failed to find updated item") |
一列都没 Set,行存在 | 只有 SELECT | Ok,什么都没写 |
第 2 行最容易误读:SET 里没有 "id",sea-orm 不是「改主键失败」,而是拿新主键去找一行不存在的记录。主键改成一个已存在的 id 时更糟:不报错,更新的是那一行,原来那一行不动。src/bin/pk_existing.rs 读出 3 号、把 id 设成 2、改 name(2.0.4):
=> Ok(Model { id: 2, sku: "b2", name: "cherry v2", qty: 5, note: None })
Model { id: 1, sku: "a1", name: "apple", qty: 3, note: None }
Model { id: 2, sku: "b2", name: "cherry v2", qty: 5, note: None }
Model { id: 3, sku: "c3", name: "cherry", qty: 7, note: None }
2 号 banana 的名字被改成了 cherry v2,3 号原样;1.1.20 输出相同。
出处
Update::one 先扫主键列,Set 或 Unchanged 都变成 WHERE pk = $n;再扫其他列,只有 Set 的进 SET,主键列一律跳过。ActiveValue 一个字段只存一个值,Unchanged(3) 被 Set(30) 覆盖后,3 就找不回来了。
执行在 Updater::exec_update_and_return_updated:is_noop(SET 为空)时改调 find_updated_model_by_id,查不到报 RecordNotFound("Failed to find updated item");否则带 RETURNING 执行,None 映射成 DbErr::RecordNotUpdated。
0.11.0 之前,UPDATE 没匹配到行报的是 DbErr::RecordNotFound("None of the database rows are affected")。PR #1425(「RecordNotFound -> RecordNotUpdated」,0.11.0)把它拆成单独的分支,理由只有一句:「This makes the behaviour consistent with RecordNotInserted」。CHANGELOG 里写成「DbErr::RecordNotFound("None of the database rows are affected") is moved to a dedicated error variant DbErr::RecordNotUpdated」。同一个版本里的 PR #1384(「Noop when update without providing any values」)加了 is_noop 那条路,它查不到行时用的仍是 RecordNotFound,于是「行不存在」从 0.11.0 起就有两种报法,取决于有没有列要写。
修法
「行不存在」当成 404:两个错误一起认。
match am.update(&db).await {
Ok(m) => println!("updated {}", m.id),
Err(DbErr::RecordNotUpdated) | Err(DbErr::RecordNotFound(_)) => println!("404: item 99 not found"),
Err(e) => return Err(e),
}
--- fix: treat both errors as "not found"
summary="UPDATE \"item\" SET \"qty\" …" db.statement="\n\nUPDATE \"item\" SET \"qty\" = $1 WHERE \"item\".\"id\" = $2 RETURNING \"id\", \"sku\", \"name\", \"qty\", \"note\"\n" rows_affected=0 rows_returned=0 elapsed=152µs elapsed_secs=0.000152
404: item 99 not found
RecordNotFound 还会来自别处(例如 exec_with_returning 找不到插入的行),这里的 match 只包住 update 一次调用,不会混淆。
改主键用 update_many,新旧两个值都由调用方给出:
let r = item::Entity::update_many()
.col_expr(item::Column::Id, Expr::value(30))
.filter(item::Column::Id.eq(3))
.exec(&db)
.await?;
--- fix: change the primary key with update_many
summary="UPDATE \"item\" SET \"id\" …" db.statement="\n\nUPDATE \"item\" SET \"id\" = $1 WHERE \"item\".\"id\" = $2\n" rows_affected=1 rows_returned=0 elapsed=431.042µs elapsed_secs=0.000431042
=> rows_affected=1
update_many 不检查影响行数,0 行时是 Ok(UpdateResult { rows_affected: 0 }),要自己看。
本意是「有就改、没有就插」时,不要靠 update 失败再 insert,用 on_conflict(..).update_columns(..) 一条语句完成,见 insert。
怎么确认没中招
- 搜
RecordNotUpdated:每一处匹配旁边都应该同时处理RecordNotFound,否则「一列都没改」的请求打到不存在的 id 上会走进通用的错误分支。 - 搜对主键字段的赋值(
am.id = Set():后面跟的是update/save时,就是表里第 2 行的情形。 - 本站目前不通过
ActiveModel::update写库,更新都用update_many,不会看到这个错误;但rows_affected == 0也不会报错,要看调用方有没有检查。
小结
RecordNotUpdated(None of the records are updated)只在至少一列Set、WHERE主键没匹配时出现。- 把主键
Set成新值不会改主键,新值进了WHERE,多半得到这个错误。 - 一列都没
Set时不发UPDATE:行存在就Ok,不存在是RecordNotFound("Failed to find updated item")。 - 2.0.4 和 1.1.20 行为相同;处理「不存在」时两个错误都要认,改主键用
update_many。