

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

# 了解 Gremlin 查询在 Neptune 中的工作方式
<a name="gremlin-explain-background"></a>

要充分利用 Amazon Neptune 中的 `explain` 和 `profile` 报告，了解一些有关 Gremlin 查询的背景信息将很有帮助。

**Topics**
+ [Neptune 中的 Gremlin 语句](gremlin-explain-background-statements.md)
+ [Neptune 如何使用语句索引处理 Gremlin 查询](gremlin-explain-background-indexing-examples.md)
+ [在 Neptune 中如何处理 Gremlin 查询](gremlin-explain-background-querying.md)

# Neptune 中的 Gremlin 语句
<a name="gremlin-explain-background-statements"></a>

Amazon Neptune 中的属性图数据由四个位置（四元组）语句组成。这些语句中的每一个都代表属性图数据的单个原子单元。有关更多信息，请参阅 [Neptune 图形数据模型](feature-overview-data-model.md)。与资源描述框架 (RDF) 数据模型类似，这四个位置如下所述：
+ `subject (S)`
+ `predicate (P)`
+ `object (O)`
+ `graph (G)`

每个语句都是对一个或多个资源的断言。例如，一个语句可以断言两个资源之间是否存在关系，或者可以将一个属性（键/值对）附加到某个资源。

您可以将谓词视为语句的动词，以描述关系或属性的类型。对象是关系的目标，或者是属性的值。图形位置可选，可通过多种不同方式使用。对于 Neptune 属性图 (PG) 数据，此项可以不使用（空图），或是用于表示边缘的标识符。一组具有共享资源标识符的语句创建一个图形。

Neptune 属性图数据模型中有三类语句：

**Topics**
+ [顶点标签语句](#gremlin-explain-background-vertex-labels)
+ [边缘语句](#gremlin-explain-background-edge-statements)
+ [属性语句](#gremlin-explain-background-property-statements)

## Gremlin 顶点标签语句
<a name="gremlin-explain-background-vertex-labels"></a>

Neptune 中的顶点标签语句有两个作用：
+ 跟踪顶点的标签。
+ 只要存在一条此类语句，即暗示图中存在特定顶点。

这些语句的主语是顶点标识符，宾语是标签，两者均由用户指定。您对这些语句使用特殊的固定谓词（显示为 `<~label>`）和默认图标识符（空图，显示为 `<~>`）。

例如，请考虑以下 `addV` 遍历。

```
g.addV("Person").property(id, "v1")
```

这种遍历导致将以下语句添加到图中。

```
StatementEvent[Added(<v1> <~label> <Person> <~>) .]
```

## Gremlin 边缘语句
<a name="gremlin-explain-background-edge-statements"></a>

Gremlin 边缘语句用于暗示 Neptune 图形中两个顶点之间存在边缘。边缘语句的主语 (S) 是源 `from` 顶点。谓词 (P) 是用户提供的边缘标签。宾语 (O) 是目标 `to` 顶点。图 (G) 是用户提供的边缘标识符。

例如，请考虑以下 `addE` 遍历。

```
g.addE("knows").from(V("v1")).to(V("v2")).property(id, "e1")
```

这种遍历导致将以下语句添加到图中。

```
StatementEvent[Added(<v1> <knows> <v2> <e1>) .]
```

## Gremlin 属性语句
<a name="gremlin-explain-background-property-statements"></a>

Neptune 中的 Gremlin 属性语句对顶点或边缘的单个属性值进行断言。主语是用户提供的顶点或边缘标识符。谓词是属性名称（键），宾语是单个属性值。图 (G) 还是默认的图标识符，即空图，显示为 `<~>`。

考虑以下顶点属性示例。

```
g.V("v1").property("name", "John")
```

该语句导致以下结果。

```
StatementEvent[Added(<v1> <name> "John" <~>) .]
```

属性语句与其他语句的不同之处在于，属性语句的宾语是基元值（`string`、`date`、`byte`、`short`、`int`、`long`、`float` 或 `double`）。它们的宾语不是可用作其他断言的主语的资源标识符。

对于多属性，集合中的每个单属性值接收各自的语句。

```
g.V("v1").property(set, "phone", "956-424-2563").property(set, "phone", "956-354-3692 (tel:9563543692)")
```

这将产生以下结果。

```
StatementEvent[Added(<v1> <phone> "956-424-2563" <~>) .]
StatementEvent[Added(<v1> <phone> "956-354-3692" <~>) .]
```

边缘属性的处理方式与顶点属性类似，但在 (S) 位置使用边标识符。例如，向边缘添加属性：

```
g.E("e1").property("weight", 0.8)
```

这会导致将以下语句添加到图表中。

```
StatementEvent[Added(<e1> <weight> 0.8 <~>) .]
```

# Neptune 如何使用语句索引处理 Gremlin 查询
<a name="gremlin-explain-background-indexing-examples"></a>

在 Amazon Neptune 中，可以通过三种语句索引访问语句，如[如何在 Neptune 中为语句编制索引](feature-overview-storage-indexing.md)中所述。Neptune 从 Gremlin 查询中提取语句*模式*，其中一些位置是已知的，其余的则留待索引搜索发现。

Neptune 假定属性图架构的大小不大。这意味着不同边缘标签和属性名称的数量相当少，导致不同谓词的总数较少。Neptune 在单独的索引中跟踪不同的谓词。它使用该谓词缓存来进行 `{ all P x POGS }` 的联合扫描，而不是使用 OSGP 索引。避免使用反向遍历 OSGP 索引可节省存储空间和加载吞吐量。

Neptune Gremlin Explain/Profile API 允许你在图表中获取谓词计数。然后，您可以确定您的应用程序是否使 Neptune 的“属性图架构较小”的假设失效。

以下示例有助于说明 Neptune 如何使用索引来处理 Gremlin 查询。

**问题：顶点 `v1` 有哪些标签？**

```
  Gremlin code:      g.V('v1').label()
  Pattern:           (<v1>, <~label>, ?, ?)
  Known positions:   SP
  Lookup positions:  OG
  Index:             SPOG
  Key range:         <v1>:<~label>:*
```

**问题：顶点 `v1` 有哪些“已知”的出边？**

```
  Gremlin code:      g.V('v1').out('knows')
  Pattern:           (<v1>, <knows>, ?, ?)
  Known positions:   SP
  Lookup positions:  OG
  Index:             SPOG
  Key range:         <v1>:<knows>:*
```

**问题：哪些顶点具有 `Person` 顶点标签？**

```
  Gremlin code:      g.V().hasLabel('Person')
  Pattern:           (?, <~label>, <Person>, <~>)
  Known positions:   POG
  Lookup positions:  S
  Index:             POGS
  Key range:         <~label>:<Person>:<~>:*
```

**问题：给定边`e1`的 from/to 顶点是什么？**

```
  Gremlin code:      g.E('e1').bothV()
  Pattern:           (?, ?, ?, <e1>)
  Known positions:   G
  Lookup positions:  SPO
  Index:             GPSO
  Key range:         <e1>:*
```

Neptune 所**没有** 的一个声明索引是反向遍历 OSGP 索引。该索引可用于收集所有边缘标签的所有入边，如以下示例所示。

**问题：什么是传入的相邻顶点 `v1`？**

```
  Gremlin code:      g.V('v1').in()
  Pattern:           (?, ?, <v1>, ?)
  Known positions:   O
  Lookup positions:  SPG
  Index:             OSGP  // <-- Index does not exist
```

# 在 Neptune 中如何处理 Gremlin 查询
<a name="gremlin-explain-background-querying"></a>

在 Amazon Neptune 中，可以通过一系列模式来表示更复杂的遍历，这些模式基于命名变量的定义创建关系，而命名变量可以在模式之间共享以创建联接。如以下示例所示。

**问题：顶点 `v1` 有哪些两跳邻域？**

```
  Gremlin code:      g.V(‘v1’).out('knows').out('knows').path()
  Pattern:           (?1=<v1>, <knows>, ?2, ?) X Pattern(?2, <knows>, ?3, ?)

  The pattern produces a three-column relation (?1, ?2, ?3) like this:
                     ?1     ?2     ?3
                     ================
                     v1     v2     v3
                     v1     v2     v4
                     v1     v5     v6
```

通过在两个模式之间共享 `?2` 变量（在第一个模式的 O 位置和第二个模式的 S 位置），可以创建从一跳邻域到二跳邻域的联接。每个 Neptune 解都有三个命名变量的绑定，这些变量可用于重新创建 [TinkerPopTraverser](http://tinkerpop.apache.org/docs/current/reference/#_the_traverser)（包括路径信息）。

```
```

[Gremlin 查询处理的第一步是将查询解析为 TinkerPop [Traversal](http://tinkerpop.apache.org/docs/current/reference/#traversal) 对象，该对象由一系列步骤组成。 TinkerPop ](http://tinkerpop.apache.org/docs/current/reference/#graph-traversal-steps)这些步骤是开源 [Apache TinkerPop 项目](http://tinkerpop.apache.org/)的一部分，既是逻辑运算符，也是物理运算符，在参考实现中构成 Gremlin 遍历。它们都用于表示查询模型。它们是可执行的运算符，可以根据其代表的运算符的语义生成解决方案。例如，`.V()`既由表示又由执行 TinkerPop [GraphStep](http://tinkerpop.apache.org/docs/current/reference/#graph-step)。

由于这些 off-the-shelf TinkerPop 步骤是可执行的，因此这样的 TinkerPop Traversal 可以执行任何 Gremlin 查询并生成正确的答案。但是，当针对大型图表执行时， TinkerPop 步骤有时效率低下且速度很慢。Neptune 不使用这些步骤，而是尝试将该遍历转换为由模式组组成的声明形式（如前所述）。

当前，Neptune 的原生查询引擎仅支持部分 Gremlin 运算符（步骤）。因此，它尝试将尽可能多的步骤折叠为一个 `NeptuneGraphQueryStep`，其中包含已转换的所有步骤的声明性逻辑查询计划。理想情况下，所有步骤都将转换。但是，当遇到无法转换的步骤时，Neptune 会脱离原生执行，并将所有查询执行从该点推迟到这些步骤。 TinkerPop 它不会尝试穿插进行本机执行。

将步骤转换成逻辑查询计划后，Neptune 运行一系列查询优化器，以根据静态分析和估计基数来重写查询计划。优化器执行多种操作，例如根据范围计数对运算符进行重新排序、删除不必要或多余的运算符、重新排列筛选条件、将运算符推入不同的组等。

生成优化的查询计划后，Neptune 创建物理运算符的管道来执行查询。这包括从语句索引中读取数据、执行各种类型的联接、筛选、排序等。管道生成解流，然后将其转换回 TinkerPop Traverser 对象流。

## 查询结果的序列化
<a name="gremlin-explain-background-querying-serialization"></a>

Amazon Neptune 目前依靠 TinkerPop 响应消息序列化器将查询结果（TinkerPop Traversers）转换为序列化数据，然后通过电线发送回客户端。这些序列化格式往往很冗长。

例如，要序列化 `g.V().limit(1)` 等顶点查询的结果，Neptune 查询引擎必须执行一次搜索来生成查询结果。但是，`GraphSON` 序列化程序将执行大量额外的搜索，才能将顶点打包为序列化格式。它必须执行一次搜索来获取标签，执行一次搜索来获取属性键，并对顶点的每个属性键执行一次搜索来获取每个键的所有值。

某些序列化格式效率更高，但是所有序列化格式都需要进行额外的搜索。此外， TinkerPop 序列化程序不会尽量避免重复搜索，这通常会导致不必要地重复许多搜索。

因此，在编写查询时，只询问所需的信息极为重要。例如，`g.V().limit(1).id()` 将仅返回顶点 ID，并消除所有其他序列化程序搜索。[Neptune 中的 Gremlin `profile` API](gremlin-profile-api.md) 允许您查看在查询执行和序列化期间进行了多少次搜索调用。