

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# Neptune 交易語義範例
<a name="transactions-examples"></a>

以下範例說明 Amazon Neptune 中交易語義的不同使用案例。

**Topics**
+ [屬性的條件式插入](#transactions-examples-conditional-insertion)
+ [屬性值唯一性](#transactions-examples-unique-property)
+ [條件式屬性變更](#transactions-examples-conditional-edit)
+ [取代屬性](#transactions-examples-replace)
+ [避免懸置元素](#transactions-examples-dangling)

## 範例 1 - 僅在屬性不存在時插入屬性
<a name="transactions-examples-conditional-insertion"></a>

假設您想要確保屬性只設定一次。例如，假設多個查詢正在嘗試同時將信用分數指派給人員。您只想要插入一個屬性執行個體，而其他查詢會失敗，因為已設定屬性。

```
# GREMLIN:
g.V('person1').hasLabel('Person').coalesce(has('creditScore'), property('creditScore', 'AAA+'))

# SPARQL:
INSERT { :person1 :creditScore "AAA+" .}
WHERE  { :person1 rdf:type :Person .
         FILTER NOT EXISTS { :person1 :creditScore ?o .} }
```

Gremlin `property()` 步驟會插入具有所指定索引鍵和值的屬性。`coalesce()` 步驟會在第一個步驟中執行第一個引數，如果失敗，則會執行第二個步驟：

在插入所指定 `person1` 頂點的 `creditScore` 屬性值之前，交易必須嘗試為 `person1` 讀取可能不存在的 `creditScore` 值。此嘗試讀取會鎖定 `SPOG` 索引中 `S=person1` 和 `P=creditScore` 的 `SP` 範圍，其中 `creditScore` 值存在或將被寫入。

採取此範圍鎖定可防止任何並行交易同時插入 `creditScore` 值。有多個平行交易時，一次最多一個平行交易可以更新該值。這會排除建立多個 `creditScore` 屬性的異常。

## 範例 2 - 宣告屬性值在全域上是唯一的
<a name="transactions-examples-unique-property"></a>

假設您想要插入社會安全號碼做為主索引鍵的人員。您想要變動查詢保證，在全域層級中，資料庫中沒有任何其他人具有相同的社會安全號碼：

```
# GREMLIN:
g.V().has('ssn', 123456789).fold()
  .coalesce(__.unfold(),
            __.addV('Person').property('name', 'John Doe').property('ssn', 123456789'))

# SPARQL:
INSERT { :person1 rdf:type :Person .
         :person1 :name "John Doe" .
         :person1 :ssn 123456789 .}
WHERE  { FILTER NOT EXISTS { ?person :ssn 123456789 } }
```

此範例與上述範例類似。主要差別在於範圍鎖定是針對 `POGS` 索引而非 `SPOG` 索引採取的。

執行查詢的交易必須讀取模式 (`?person :ssn 123456789`)，其中繫結 `P` 和 `O` 位置。範圍鎖定是針對 `P=ssn` 和 `O=123456789` 的 `POGS` 索引採取的。
+ 如果模式的確存在，則不會採取任何動作。
+ 如果不存在，鎖定會防止任何並行交易也插入該社會安全號碼

## 範例 3 - 如果另一個屬性具有指定值，則變更屬性
<a name="transactions-examples-conditional-edit"></a>

假設遊戲中的各種事件將人物從第一層移到第二層，並將設為零的新 `level2Score` 屬性指派給他們。您需要確保這類交易的多個並行執行個體無法建立層級二分數屬性的多個執行個體。Gremlin 和 SPARQL 中的查詢可能看起來如下所示。

```
# GREMLIN:
g.V('person1').hasLabel('Person').has('level', 1)
 .property('level2Score', 0)
 .property(Cardinality.single, 'level', 2)

# SPARQL:
DELETE { :person1 :level 1 .}
INSERT { :person1 :level2Score 0 .
         :person1 :level 2 .}
WHERE  { :person1 rdf:type :Person .
         :person1 :level 1 .}
```

在 Gremlin 中，指定 `Cardinality.single` 時，`property()` 步驟會新增屬性，或將現有的屬性值取代為指定的新值。

屬性值的任何更新 (例如將 `level` 從 1 增加到 2) 會實作為刪除目前記錄，並使用新的屬性值插入新記錄。在此情況下，會刪除層級編號 1 的記錄，並重新插入層級編號 2 的記錄。

若要讓交易能夠新增 `level`，並將 `level2Score` 從 1 更新到 2，必須先驗證 `level` 值目前是否等於 1。執行此動作時，它會對 `SPOG` 索引中 `S=person1`、`P=level` 和 `O=1` 的 `SPO` 字首採取範圍鎖定。此鎖定可防止並行交易刪除版本 1 三元組，因此不可能發生起衝突的並行更新。

## 範例 4 - 取代現有屬性
<a name="transactions-examples-replace"></a>

特定事件可能會將人員的信用分數更新為新值 (這裡指的是 `BBB`)。但您想要確保該類型的並行事件無法為人員建立多個信用分數屬性。

```
# GREMLIN:
g.V('person1').hasLabel('Person')
 .sideEffect(properties('creditScore').drop())
 .property('creditScore', 'BBB')

# SPARQL:
DELETE { :person1 :creditScore ?o .}
INSERT { :person1 :creditScore "BBB" .}
WHERE  { :person1 rdf:type :Person .
         :person1 :creditScore ?o .}
```

此案例與範例 3 類似，差別在於不是鎖定 `SPO` 字首，而是 Neptune 只會鎖定具有 `S=person1` 和 `P=creditScore` 的 `SP` 字首。這可防止並行交易插入或刪除任何具有 `person1` 主旨之 `creditScore` 屬性的三元組。

## 範例 5 - 避免懸置屬性或邊緣
<a name="transactions-examples-dangling"></a>

實體上的更新不應留下懸置元素，也就是與未輸入之實體相關聯的屬性或邊緣。這只是 SPARQL 中的問題，因為 Gremlin 具有內建限制條件來防止懸置元素。

```
# SPARQL:
tx1: INSERT { :person1 :age 23 } WHERE { :person1 rdf:type :Person }
tx2: DELETE { :person1 ?p ?o }
```

`INSERT` 查詢必須讀取和鎖定 `SPOG` 索引 `O=Person` 中具有 `S=person1` 和 `P=rdf:type`,的 `SPO` 字首。鎖定可防止 `DELETE` 查詢平行成功。

在這兩個查詢 (嘗試刪除 `:person1 rdf:type :Person` 記錄的 `DELETE` 查詢，以及讀取記錄並在 `SPOG` 索引中的 `SPO` 上建立範圍的 `INSERT` 查詢) 之間的競爭中，可能會產生以下結果：
+ 如果 `INSERT` 查詢在 `DELETE` 查詢讀取並刪除 `:person1` 的所有記錄之前遞交，則 `:person1` 會完全從資料庫中移除，包括新插入的記錄。
+ 如果 `DELETE` 查詢在 `INSERT` 查詢嘗試讀取 `:person1 rdf:type :Person` 記錄之前遞交，則讀取會觀察遞交的變更。也就是說，它找不到任何 `:person1 rdf:type :Person` 記錄，因此變成無操作。
+ 如果 `INSERT` 查詢在 `DELETE` 查詢讀取之前讀取，則 `:person1 rdf:type :Person` 三元組會遭到鎖定，而且 `DELETE` 查詢會遭到封鎖，直到 INSERT 查詢遞交為止，如先前的第一個案例一樣。
+ 如果 `DELETE` 在 `INSERT` 查詢之前讀取，而且 `INSERT` 查詢嘗試讀取，並對記錄的 `SPO` 字首採取鎖定，則會偵測到衝突。這是因為三元組已標記為移除，然後 `INSERT` 失敗。

在所有這些不同的可能事件序列中，不會建立懸置邊緣。