

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 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 关的记录。

在增加 `level2Score` 并将 `level` 从 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 相似，不同之处在于，Neptune 不是锁定 `SPO` 前缀，而是仅使用 `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` 索引中的 `S=person1`、`P=rdf:type` 和 `O=Person` 锁定 `SPO` 前缀。该锁可防止 `DELETE` 查询并行成功。

在 `DELETE` 查询尝试删除 `:person1 rdf:type :Person` 记录和 `INSERT` 查询读取该记录并在 `SPOG` 索引中该记录的 `SPO` 上创建范围锁的争用过程中，可能产生以下结果：
+ 如果 `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` 将失败。

在上述所有不同的可能事件序列中，均未创建任何悬垂边缘。