---
# System prepended metadata

title: MongoDB - Mongoose Version Error 的成因
tags: [Blue 的學習紀錄]

---

---
tags: Blue 的學習紀錄
---

# MongoDB - Mongoose Version Error 的成因

錯誤訊息：
`VersionError: No matching document found for id "xxxxxxxxxxxxxxx"`

## _v

透過 Mongoose 存取 MongoDB 時，若觀察儲存在資料庫裡的欄位，會發現自動帶有 `_v` 這個欄位

這個欄位的名字叫做 Version Key
Mongoose 官方文件介紹：https://mongoosejs.com/docs/guide.html#versionKey

這個 key 的作用是讓 Mongoose 對資料做版本控制，每當成功執行 `save()` 時，`_v` 就會 +1

以下直接以範例來說明，Version Key 如何做簡單的版本控制

:::info
這邊指的"版本控制"，並不是像 git 這種強大的版本控制工具
而只是一種簡單的，能夠對一筆資料的版本做判斷而已
:::

## 觸發錯誤的範例

```sequence
participant MongoDB
participant Client
# 資料庫
Note left of MongoDB: 一筆已存在 DB 裡的資料，_v = 0
# 開始讀取
MongoDB -> Client: Client A 讀取資料
Note right of Client: Client A 取得的 _v = 0
MongoDB -> Client: Client B 讀取資料
Note right of Client: Client B 取得的 _v = 0
# 開始寫入
Client -> MongoDB: Client B 寫入資料（傳入的 _v = 0）
Note left of MongoDB: DB 裡的 _v 值等於傳入 _v 值，寫入成功，_v += 1
Note left of MongoDB: _v = 1
Client -> MongoDB: Client A 寫入資料（傳入的 _v = 0）
Note left of MongoDB: DB 裡的 _v 值不等於傳入 _v 值，寫入失敗
Note left of MongoDB: 發生 Version Error！
```

假設原來的資料是 `{ a:0, b:0 }`
Client B 寫入的是 `{ a:0, b:1 }`
Client A 寫入的是 `{ a:1, b:0 }`

若沒有 Version Key，就可能發生 Client B 所做的改變被 Client A 無意間給覆蓋掉

原本期待的最終結果是 `{ a:1, b:1 }`，結果卻只有 Client A 的輸入 `{ a:1, b:0 }` 有被寫入


:::warning
根據 Mongoose 文件的說明，只有 `save()` 才會檢查 Version Key
:::

## 直接 `update()`？

使用 Mongoose 做 `updateOne()` or `updateMany()` 的話，就不會檢查 Version Key，直接進行寫入，不會發生 Version Error

而且 `update()` 的執行效率也比較好，因為少了存取，直接就進行寫入

但對於覆蓋他筆寫入的風險，就得自行掌控

## 用 `save()` 的話，如何避免 Version Error？

以我目前所知：

- catch version error
  在程式碼去 catch 這種 error，自動 retry
- 在存取資料前，對資料加上 Lock，防止同時有其他人做改動
  怎麼加 Lock 我不熟悉，只是知道可以這樣做而已

## 後記

其實這只是[樂觀鎖](https://hackmd.io/@vegetableBird/SkOxBHJw9)將資料鎖住了的情況，並不是什麼錯誤
