None of the records are inserted
主线位置:写入主线的 insert 分支,
RETURNING之后。Postgres 上Insert::exec发INSERT ... RETURNING "id",从返回的行里取主键;ON CONFLICT DO NOTHING撞上冲突时一行也不返回,sea-orm 只好报错。各个exec的区别见 insert 的「四个 exec,三种 RETURNING」和「on_conflict:upsert 与 DO NOTHING」,这一页把「按设计什么都不做」被当成错误的几种样子放在一起。
flowchart TB
ins["INSERT ON CONFLICT DO NOTHING"] --> rows{"返回了行"}
rows -->|有| okr["Ok"]
rows -->|没有| which{"哪个 exec"}
which -->|"Insert exec"| e1[RecordNotInserted]
which -->|"exec_with_returning"| e2["RecordNotFound"]
which -->|"exec_without_returning"| zero["Ok 0"]
which -->|"TryInsert exec"| conf["Ok Conflicted"]
本页实验在 M2 Max、macOS、rustc 1.99.0、Postgres 16.14 上运行,sea-orm 2.0.4,以及本站用的 1.1.20。表 item 有 id serial 主键和 sku text UNIQUE,a1 已经存在。SQL 取自 sqlx 的语句日志(sqlx=info)。
现象
一个「已存在就跳过」的插入,第二次调用返回错误:
None of the records are inserted
这是 DbErr::RecordNotInserted 的 Display(error.rs 里的 #[error("None of the records are inserted")])。换成 exec_with_returning 是另一个错误:RecordNotFound("Failed to find inserted item")。
最小复现
src/bin/on_conflict.rs,每段用同一个 ActiveModel(sku = "a1"):
let nothing = OnConflict::column(item::Column::Sku).do_nothing().to_owned();
// 1.
item::Entity::insert(am("a1")).on_conflict(nothing.clone()).exec(&db).await;
// 2.
item::Entity::insert(am("a1")).on_conflict(nothing.clone()).exec_with_returning(&db).await;
// 3.
item::Entity::insert(am("a1")).on_conflict(nothing.clone()).exec_without_returning(&db).await;
// 4.
item::Entity::insert(am("a1")).on_conflict_do_nothing_on([item::Column::Sku]).exec(&db).await;
// 5.
item::Entity::insert(am("a1")).on_conflict_do_nothing_on([item::Column::Sku]).exec_with_returning(&db).await;
// 6.
item::Entity::insert(am("a1")).on_conflict_do_nothing().exec(&db).await;
2.0.4 的输出(第 2 段起的 sqlx 日志只留 SQL 不同的几行):
--- 1. on_conflict(..do_nothing()), Insert::exec
summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2) ON CONFLICT (\"sku\") DO NOTHING RETURNING \"id\"\n" rows_affected=0 rows_returned=0 elapsed=1.52675ms elapsed_secs=0.00152675
=> Err(RecordNotInserted)
Display: None of the records are inserted
--- 2. on_conflict(..do_nothing()), Insert::exec_with_returning
summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2) ON CONFLICT (\"sku\") DO NOTHING RETURNING \"id\", \"sku\", \"name\", \"qty\", \"note\"\n" rows_affected=0 rows_returned=0 elapsed=335.041µs elapsed_secs=0.000335041
=> Err(RecordNotFound("Failed to find inserted item"))
--- 3. on_conflict(..do_nothing()), Insert::exec_without_returning
summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2) ON CONFLICT (\"sku\") DO NOTHING\n" rows_affected=0 rows_returned=0 elapsed=306.708µs elapsed_secs=0.000306708
=> Ok(0)
--- 4. on_conflict_do_nothing_on([Sku]), TryInsert::exec
...
=> Ok(Conflicted)
--- 5. on_conflict_do_nothing_on([Sku]), TryInsert::exec_with_returning
...
=> Err(RecordNotFound("Failed to find inserted item"))
--- 6. on_conflict_do_nothing() targets the primary key, sku clashes
summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2) ON CONFLICT (\"id\") DO NOTHING RETURNING \"id\"\n" rows_affected=0 rows_returned=0 elapsed=408.625µs elapsed_secs=0.000408625
=> Err Query Error: error returned from database: duplicate key value violates unique constraint "item_sku_key" at line 666
第 1、4 段发的是同一条 SQL,结果一个是错误、一个是 Ok(Conflicted)。第 5 段用了 TryInsert 也没用。第 6 段的冲突目标是 "id",sku 撞了照样是 23505。
1.1.20 没有 on_conflict_do_nothing_on,TryInsert 用 .on_conflict(..).do_nothing() 拿到,六段的结果与 2.0.4 逐条相同(sqlx 日志行删掉了):
--- 1. on_conflict(..do_nothing()), Insert::exec
=> Err(RecordNotInserted)
Display: None of the records are inserted
--- 2. on_conflict(..do_nothing()), Insert::exec_with_returning
=> Err(RecordNotFound("Failed to find inserted item"))
--- 3. on_conflict(..do_nothing()), Insert::exec_without_returning
=> Ok(0)
--- 4. on_conflict(..).do_nothing(), TryInsert::exec
=> Ok(Conflicted)
--- 5. on_conflict(..).do_nothing(), TryInsert::exec_with_returning
=> Err(RecordNotFound("Failed to find inserted item"))
--- 6. on_conflict_do_nothing() targets the primary key, sku clashes
=> Err Query Error: error returned from database: duplicate key value violates unique constraint "item_sku_key"
出处
exec_insert:支持RETURNING时query_all后rows.pop(),没有行就return Err(DbErr::RecordNotInserted)。它不知道语句里有没有DO NOTHING,「冲突」和「真的没插进去」对它是一回事。exec_insert_with_returning:.one(db)拿到None时报RecordNotFound("Failed to find inserted item")。TryInsert::exec把RecordNotInserted翻译成TryInsertResult::Conflicted;TryInsert::exec_with_returning翻译的也是RecordNotInserted,可它调的exec_insert_with_returning报的是RecordNotFound,所以Conflicted在这条路上永远出不来。exec_insert_without_returning只返回rows_affected,冲突时是 0,不报错。Insert::on_conflict_do_nothing的冲突目标是实体的主键列;on_conflict_do_nothing_on才能指定唯一列。主键是serial、插入时不带主键的表,前者几乎不可能冲突。
exec_with_returning 这条在上游是 issue #2389(「postgres, RecordNotFound Error: Failed to find inserted item」,insert_many(..).on_conflict(..do_nothing()).exec_with_returning),2025-12 以「The postgres issue should have been solved in 2.0」关闭,同时加了一个测试(changelog/2.0.0-rc.23.md:「Added test coverage and fixes for insert_many with on_conflict_do_nothing and returning clauses」)。2.0.4 上实测(src/bin/on_conflict_many.rs):只有不经过 TryInsert 的那条路修好了。
--- insert_many([a1]).on_conflict(..do_nothing()).exec_with_returning
summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2) ON CONFLICT (\"sku\") DO NOTHING RETURNING \"id\", \"sku\", \"name\", \"qty\", \"note\"\n" rows_affected=0 rows_returned=0 elapsed=2.3465ms elapsed_secs=0.0023465
=> Ok([])
--- insert_many([a1]).on_conflict_do_nothing_on([Sku]).exec_with_returning
summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2) ON CONFLICT (\"sku\") DO NOTHING RETURNING \"id\", \"sku\", \"name\", \"qty\", \"note\"\n" rows_affected=0 rows_returned=0 elapsed=269.458µs elapsed_secs=0.000269458
=> Err(RecordNotFound("Failed to find inserted item"))
unwrap panicissue #1175(0.10.1)报的是同一个场景:「Running the following query twice with the same value will yield no return result in the second run」,结果是 called Option::unwrap() on a None value,panic 在 exec_insert 取返回行的地方。它被并进 issue #899,由 PR #1021 修掉(0.11.0),PR 写的是「throw DbErr::RecordNotInserted if none of the records are being inserted」:panic 变成了错误,就是本页标题。「什么都不做」仍然是错误,于是 PR #1712(0.12.1,CHANGELOG:「Insert on conflict do nothing to return Ok」)给 TryInsert 加了 Conflicted,旧的 Insert::exec 行为保持不变。到 2.0.4,这两条路并存。
修法
要区分「插入了」和「已存在」:用 TryInsert,按结果分支。 2.0 用 on_conflict_do_nothing_on([唯一列]),1.1.20 用 .on_conflict(..).do_nothing():
for sku in ["a1", "z9"] {
match item::Entity::insert(am(sku)).on_conflict_do_nothing_on([item::Column::Sku]).exec(&db).await? {
TryInsertResult::Inserted(r) => println!("{sku}: inserted, id {}", r.last_insert_id),
TryInsertResult::Conflicted => println!("{sku}: already there"),
TryInsertResult::Empty => println!("{sku}: nothing to insert"),
}
}
--- fix: match on TryInsertResult
summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2) ON CONFLICT (\"sku\") DO NOTHING RETURNING \"id\"\n" rows_affected=0 rows_returned=0 elapsed=151.667µs elapsed_secs=0.000151667
a1: already there
summary="INSERT INTO \"item\" (\"sku\", …" db.statement="\n\nINSERT INTO \"item\" (\"sku\", \"name\") VALUES ($1, $2) ON CONFLICT (\"sku\") DO NOTHING RETURNING \"id\"\n" rows_affected=1 rows_returned=1 elapsed=310.916µs elapsed_secs=0.000310916
z9: inserted, id 11
1.1.20 上输出相同。
只要幂等、不关心插没插:exec_without_returning,冲突时 Ok(0),不报错。
要整行:不要用单行的 exec_with_returning。 2.0 的 InsertMany::exec_with_returning(不经过 TryInsert)冲突时是 Ok([]);或者 TryInsert::exec 拿到 Conflicted 后再按唯一列查一次。
怎么确认没中招
- 搜
do_nothing,看它后面接的是哪个exec:Insert::exec和任何exec_with_returning在冲突时都返回Err。本站的grant_entitlement_internal在购买时用do_nothing(),配的是exec_without_returning,重复购买得到Ok(0),不受影响。 - 搜
on_conflict_do_nothing()(不带_on):冲突目标是主键,确认这正是你要的;按业务唯一键去重要写on_conflict_do_nothing_on。 - 匹配错误时不要只认
RecordNotInserted:同一个冲突在exec_with_returning上是RecordNotFound。
小结
ON CONFLICT DO NOTHING冲突时 Postgres 不返回行;Insert::exec报RecordNotInserted(None of the records are inserted),exec_with_returning(包括TryInsert的)报RecordNotFound("Failed to find inserted item")。- 只有
TryInsert::exec给Ok(Conflicted),exec_without_returning给Ok(0);2.0 的InsertMany::exec_with_returning给Ok([])。 on_conflict_do_nothing()的目标是主键,按唯一列要用on_conflict_do_nothing_on。- 2.0.4 和 1.1.20 行为相同;issue #2389 关闭了,但
TryInsert::exec_with_returning那条路在 2.0.4 上仍是RecordNotFound。