

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

# 使用 openCypher 和 Bolt 的 Neptune 最佳實務
openCypher 和 Bolt

搭配 Neptune 使用 openCypher 查詢語言和 Bolt 通訊協定時，請遵循這些最佳實務。如需在 Neptune 使用 openCypher 的相關資訊，請參閱 [使用 openCypher 存取 Neptune 圖形](access-graph-opencypher.md)。

**Topics**
+ [

## 在容錯移轉之後建立新連線
](#best-practices-opencypher-renew-connection)
+ [

## 適用於長期命應用的連線處理
](#best-practices-opencypher-long-connections)
+ [

## 的連線處理 AWS Lambda
](#best-practices-opencypher-lambda-connections)
+ [

# 偏好查詢中的定向至雙向邊緣
](best-practices-opencypher-directed-edges.md)
+ [

# Neptune 不支援交易中的多個並行查詢
](best-practices-opencypher-multiple-queries.md)
+ [

# 完成後關閉驅動程式物件
](best-practices-opencypher-close-driver.md)
+ [

# 使用明確的交易模式進行讀取和寫入
](best-practices-opencypher-use-explicit-txs.md)
+ [

# 例外狀況的重試邏輯
](best-practices-opencypher-retry-logic.md)
+ [

# 使用單一 SET 子句一次設定多個屬性
](best-practices-content-0.md)
+ [

# 使用參數化查詢
](best-practices-content-2.md)
+ [

# 在 UNWIND 子句中使用平面貼圖而非巢狀貼圖
](best-practices-content-3.md)
+ [

# 在可變長度路徑 (VLP) 表達式中，將更嚴格的節點放在左側
](best-practices-content-4.md)
+ [

# 使用精細的關係名稱避免備援節點標籤檢查
](best-practices-content-5.md)
+ [

# 盡可能指定邊緣標籤
](best-practices-content-6.md)
+ [

# 盡可能避免使用 WITH 子句
](best-practices-content-7.md)
+ [

# 儘早在查詢中放置限制性篩選條件
](best-practices-content-8.md)
+ [

# 明確檢查屬性是否存在
](best-practices-content-9.md)
+ [

# 請勿使用具名路徑 （除非必要）
](best-practices-content-10.md)
+ [

# 避免 COLLECT(DISTINCT())
](best-practices-content-11.md)
+ [

# 擷取所有屬性值時，偏好個別屬性查詢的屬性函數
](best-practices-content-12.md)
+ [

# 在查詢之外執行靜態運算
](best-practices-content-13.md)
+ [

# 使用 UNWIND 而非個別陳述式的批次輸入
](best-practices-content-14.md)
+ [

# 偏好使用節點/關係IDs
](best-practices-content-15.md)
+ [

# 避免在查詢中進行 \$1id 運算
](best-practices-content-16.md)
+ [

# 更新/合併多個節點
](best-practices-merge-multiple-nodes.md)

## 在容錯移轉之後建立新連線
在容錯移轉之後重新連線

若發生容錯移轉，Bolt 驅動程式可以繼續連線至舊的寫入器執行個體，而不是新的作用中執行個體，因為 DNS 名稱已解析為特定 IP 地址。

若要避免這種情況發生，請在任何容錯移轉之後關閉 `Driver` 物件，然後重新連線該物件。

## 適用於長期命應用的連線處理
重複使用驅動程式物件

建置長期應用程式 (例如在容器內或在 Amazon EC2 執行個體上執行的應用程式) 時，請將 `Driver` 物件執行個體化一次，然後在應用程式的生命週期內重複使用該物件。`Driver` 物件是安全執行緒，初始化執行緒的額外負荷很大。

## 的連線處理 AWS Lambda
Lambda 連線處理

由於 Bolt 驅動程式的連線額外負荷和管理需求，因此不建議在 AWS Lambda 函數內使用。請改用 [HTTPS 端點](access-graph-opencypher-queries.md)。

# 偏好查詢中的定向至雙向邊緣
偏好定向邊緣

Neptune 執行查詢最佳化時，雙向邊緣會使建立最佳化查詢計劃變得困難。次佳計劃要求引擎執行不必要的工作並導致更差的效能。

因此，盡可能使用定向邊緣而不是雙向邊緣。例如，使用：

```
MATCH p=(:airport {code: 'ANC'})-[:route]->(d) RETURN p)
```

而不是：

```
MATCH p=(:airport {code: 'ANC'})-[:route]-(d) RETURN p)
```

大多數資料模型實際上不需要周遊兩個方向的邊緣，因此查詢可以透過切換到使用定向邊緣來達到大幅的效能改進。

如果您的資料模型確實需要周遊雙向邊緣，則使 `MATCH` 模式中的第一個節點 (左側) 成為篩選最嚴格的節點。

舉個例子，「為我找到所有往返 `ANC` 機場的 `routes`」。編寫此查詢，從 `ANC` 機場開始，如下所示：

```
MATCH p=(src:airport {code: 'ANC'})-[:route]-(d) RETURN p
```

引擎可以執行最少的工作量來滿足查詢，因為受到最多限制的節點會放置在模式中作為第一個節點 (左側)。然後，引擎可以最佳化查詢。

這比在模式結束時篩選 `ANC` 機場更好，如下所示：

```
MATCH p=(d)-[:route]-(src:airport {code: 'ANC'}) RETURN p
```

當受到最多限制的節點未放在模式中的第一位時，引擎必須執行額外的工作，因為它無法最佳化查詢，而且必須執行其他查詢才能得出結果。

# Neptune 不支援交易中的多個並行查詢
沒有並行交易查詢

雖然 Bolt 驅動程式本身允許交易中的並行查詢，但 Neptune 不支援交易中的多個查詢同時執行。相反，Neptune 會要求交易中的多個查詢循序執行，並且在啟動下一個查詢之前，完全耗用每個查詢的結果。

以下範例說明如何使用 Bolt 在交易中循序執行多個查詢，以便在下一個查詢開始之前完全耗用每個查詢的結果：

```
final String query = "MATCH (n) RETURN n";

try (Driver driver = getDriver(HOST_BOLT, getDefaultConfig())) {
  try (Session session = driver.session(readSessionConfig)) {
    try (Transaction trx = session.beginTransaction()) {
      final Result res_1 = trx.run(query);
      Assert.assertEquals(10000, res_1.list().size());
      final Result res_2 = trx.run(query);
      Assert.assertEquals(10000, res_2.list().size());
    }
  }
}
```

# 完成後關閉驅動程式物件
關閉驅動程式物件

完成後請務必關閉用戶端，以便伺服器關閉 Bolt 連線，以及釋放與連線相關聯的所有資源。如果您使用 `driver.close()` 關閉驅動程式，則此情況會自動發生。

如果驅動程式未正確關閉，Neptune 會在 20 分鐘後終止所有閒置的 Bolt 連線，或者如果您使用 IAM 驗證，則會在 10 天後終止。

Neptune 支援不超過 1000 個並行 Bolt 連線。如果您在完成後未明確關閉連線，且即時連線數目達到 1000 限制，則任何新的連線嘗試都會失敗。

# 使用明確的交易模式進行讀取和寫入
使用明確交易模式

使用交易搭配 Neptune 和 Bolt 驅動程式時，最好將讀取和寫入交易的存取模式明確設定為正確的設定。

## 唯讀交易
讀取交易

對於唯讀交易，如果您在建置工作階段時未傳入適當的存取模式組態，則會使用預設隔離層級，也就是變動查詢隔離。因此，對於唯讀交易來說，將存取模式明確設定為 `read` 很重要。

**自動遞交讀取交易範例：**

```
SessionConfig sessionConfig = SessionConfig
  .builder()
  .withFetchSize(1000)
  .withDefaultAccessMode(AccessMode.READ)
  .build();
Session session = driver.session(sessionConfig);
try {
  (Add your application code here)
} catch (final Exception e) {
  throw e;
} finally {
  driver.close()
}
```

**讀取交易範例：**

```
Driver driver = GraphDatabase.driver(url, auth, config);
SessionConfig sessionConfig = SessionConfig
  .builder()
  .withDefaultAccessMode(AccessMode.READ)
  .build();
driver.session(sessionConfig).readTransaction(
  new TransactionWork<List<String>>() {
    @Override
    public List<String> execute(org.neo4j.driver.Transaction tx) {
      (Add your application code here)
    }
  }
);
```

在這兩種情況下，[`SNAPSHOT` 隔離](transactions-isolation-levels.md)都是使用 [Neptune 唯讀交易語義](transactions-neptune.md#transactions-neptune-read-only)來達成的。

因為僅供讀取複本只接受唯讀查詢，所以提交至僅供讀取複本的任何查詢都會在 `SNAPSHOT` 隔離語義下執行。

唯讀交易沒有已變更讀取或不可重複讀取。

## 變動交易
變動交易

對於變動查詢，有三種不同的機制來建立寫入交易，每個方法描述如下：

**隱含寫入交易範例：**

```
Driver driver = GraphDatabase.driver(url, auth, config);
SessionConfig sessionConfig = SessionConfig
  .builder()
  .withDefaultAccessMode(AccessMode.WRITE)
  .build();
driver.session(sessionConfig).writeTransaction(
  new TransactionWork<List<String>>() {
    @Override
    public List<String> execute(org.neo4j.driver.Transaction tx) {
      (Add your application code here)
    }
  }
);
```

**自動遞交寫入交易範例：**

```
SessionConfig sessionConfig = SessionConfig
  .builder()
  .withFetchSize(1000)
  .withDefaultAccessMode(AccessMode.Write)
  .build();
Session session = driver.session(sessionConfig);
try {
  (Add your application code here)
} catch (final Exception e) {
    throw e;
} finally {
    driver.close()
}
```

**明確寫入交易範例：**

```
Driver driver = GraphDatabase.driver(url, auth, config);
SessionConfig sessionConfig = SessionConfig
  .builder()
  .withFetchSize(1000)
  .withDefaultAccessMode(AccessMode.WRITE)
  .build();
Transaction beginWriteTransaction = driver.session(sessionConfig).beginTransaction();
  (Add your application code here)
beginWriteTransaction.commit();
driver.close();
```

**寫入交易的隔離層級**
+ 作為變動查詢一部分進行的讀取會在 `READ COMMITTED` 交易隔離下執行。
+ 作為變動查詢一部分進行的讀取沒有任何已讀取讀取。
+ 在變動查詢中讀取時，會鎖定記錄和記錄範圍。
+ 當變動交易讀取某個索引範圍時，強力保證在讀取結束之前，任何並行交易都不會修改此範圍。

變動查詢不是執行緒安全的。

如需衝突，請參閱 [使用鎖定等待逾時的衝突解決機制](transactions-neptune.md#transactions-neptune-conflicts)。

變動查詢不會在失敗的情況下自動重試。

# 例外狀況的重試邏輯
重試邏輯

對於允許重試的所有例外狀況，通常最好使用[指數退避和重試策略](https://docs.aws.amazon.com/general/latest/gr/api-retries.html)，該策略會在重試之間提供逐漸加長的等待時間，以便更好地處理暫時性問題，例如 `ConcurrentModificationException` 錯誤。下面顯示指數退避和重試模式的範例：

```
public static void main() {
  try (Driver driver = getDriver(HOST_BOLT, getDefaultConfig())) {
    retriableOperation(driver, "CREATE (n {prop:'1'})")
        .withRetries(5)
        .withExponentialBackoff(true)
        .maxWaitTimeInMilliSec(500)
        .call();
  }
}

protected RetryableWrapper retriableOperation(final Driver driver, final String query){
  return new RetryableWrapper<Void>() {
    @Override
    public Void submit() {
      log.info("Performing graph Operation in a retry manner......");
      try (Session session = driver.session(writeSessionConfig)) {
        try (Transaction trx =  session.beginTransaction()) {
            trx.run(query).consume();
            trx.commit();
        }
      }
      return null;
    }

    @Override
    public boolean isRetryable(Exception e) {
      if (isCME(e)) {
        log.debug("Retrying on exception.... {}", e);
        return true;
      }
      return false;
    }

    private boolean isCME(Exception ex) {
      return ex.getMessage().contains("Operation failed due to conflicting concurrent operations");
    }
  };
}



/**
 * Wrapper which can retry on certain condition. Client can retry operation using this class.
 */
@Log4j2
@Getter
public abstract class RetryableWrapper<T> {

  private long retries = 5;
  private long maxWaitTimeInSec = 1;
  private boolean exponentialBackoff = true;

  /**
   * Override the method with custom implementation, which will be called in retryable block.
   */
  public abstract T submit() throws Exception;

  /**
   * Override with custom logic, on which exception to retry with.
   */
  public abstract boolean isRetryable(final Exception e);

  /**
   * Define the number of retries.
   *
   * @param retries -no of retries.
   */
  public RetryableWrapper<T> withRetries(final long retries) {
    this.retries = retries;
    return this;
  }

  /**
   * Max wait time before making the next call.
   *
   * @param time - max polling interval.
   */
  public RetryableWrapper<T> maxWaitTimeInMilliSec(final long time) {
    this.maxWaitTimeInSec = time;
    return this;
  }

  /**
   * ExponentialBackoff coefficient.
   */
  public RetryableWrapper<T> withExponentialBackoff(final boolean expo) {
    this.exponentialBackoff = expo;
    return this;
  }

  /**
   * Call client method which is wrapped in submit method.
   */
  public T call() throws Exception {
    int count = 0;
    Exception exceptionForMitigationPurpose = null;
    do {
      final long waitTime = exponentialBackoff ? Math.min(getWaitTimeExp(retries), maxWaitTimeInSec) : 0;
      try {
          return submit();
      } catch (Exception e) {
        exceptionForMitigationPurpose = e;
        if (isRetryable(e) && count < retries) {
          Thread.sleep(waitTime);
          log.debug("Retrying on exception attempt - {} on exception cause - {}", count, e.getMessage());
        } else if (!isRetryable(e)) {
          log.error(e.getMessage());
          throw new RuntimeException(e);
        }
      }
    } while (++count < retries);

    throw new IOException(String.format(
          "Retry was unsuccessful.... attempts %d. Hence throwing exception " + "back to the caller...", count),
          exceptionForMitigationPurpose);
  }

  /*
   * Returns the next wait interval, in milliseconds, using an exponential backoff
   * algorithm.
   */
  private long getWaitTimeExp(final long retryCount) {
    if (0 == retryCount) {
      return 0;
    }
    return ((long) Math.pow(2, retryCount) * 100L);
  }
}
```

# 使用單一 SET 子句一次設定多個屬性
使用 SET 子句

 使用映射一次為實體設定多個屬性，而不是使用多個 SET 子句來設定個別屬性。

 您可以使用：

```
MATCH (n:SomeLabel {`~id`: 'id1'})
SET n += {property1 : 'value1',
property2 : 'value2',
property3 : 'value3'}
```

 而不是：

```
MATCH (n:SomeLabel {`~id`: 'id1'})
SET n.property1 = 'value1'
SET n.property2 = 'value2'
SET n.property3 = 'value3'
```

 SET 子句接受單一屬性或映射。如果更新單一實體上的多個屬性，使用具有映射的單一 SET 子句允許在單一操作中執行更新，而不是以更有效率的方式執行多個操作。

## 使用 SET 子句一次移除多個屬性


 使用 openCypher 語言時，REMOVE 用於從實體移除屬性。在 Neptune 中，每個要移除的屬性都需要個別的操作，以新增查詢延遲。您可以改為使用 SET 搭配映射，將所有屬性值設定為 `null`，其在 Neptune 中等同於移除屬性。需要移除單一實體上的多個屬性時，Neptune 將提高效能。

使用：

```
WITH {prop1: null, prop2: null, prop3: null} as propertiesToRemove 
MATCH (n) 
SET n += propertiesToRemove
```

而不是：

```
MATCH (n) 
REMOVE n.prop1, n.prop2, n.prop3
```

# 使用參數化查詢


 建議在使用 openCypher 查詢時一律使用參數化查詢。查詢引擎可以針對查詢計畫快取等功能利用重複參數化查詢，其中使用不同參數重複調用相同的參數化結構可以利用快取計畫。只有在參數化查詢在 100 毫秒內完成且參數類型為 NUMBER、BOOLEAN 或 STRING 時，才會快取和重複使用針對參數化查詢產生的查詢計劃。

使用：

```
MATCH (n:foo) WHERE id(n) = $id RETURN n
```

使用參數：

```
parameters={"id": "first"}
parameters={"id": "second"}
parameters={"id": "third"}
```

而不是：

```
MATCH (n:foo) WHERE id(n) = "first" RETURN n
MATCH (n:foo) WHERE id(n) = "second" RETURN n
MATCH (n:foo) WHERE id(n) = "third" RETURN n
```

# 在 UNWIND 子句中使用平面貼圖而非巢狀貼圖
UNWIND clase

 深度巢狀結構可以限制查詢引擎產生最佳查詢計畫的能力。為了部分緩解此問題，下列定義的模式會針對下列案例建立最佳計劃：
+  案例 1：UNWIND 與 cypher 常值清單，其中包括 NUMBER、STRING 和 BOOLEAN。
+  案例 2：UNWIND 搭配平面貼圖清單，其中僅包含做為值的字元常值 (NUMBER、STRING、BOOLEAN)。

 撰寫包含 UNWIND 子句的查詢時，請使用上述建議來改善效能。

案例 1 範例：

```
UNWIND $ids as x
MATCH(t:ticket {`~id`: x})
```

使用參數：

```
parameters={
  "ids": [1, 2, 3]
}
```

 案例 2 的範例是為 CREATE 或 MERGE 產生節點清單。與其發出多個陳述式，請使用下列模式將屬性定義為一組平面貼圖：

```
UNWIND $props as p
CREATE(t:ticket {title: p.title, severity:p.severity})
```

使用參數：

```
parameters={
  "props": [
    {"title": "food poisoning", "severity": "2"},
    {"title": "Simone is in office", "severity": "3"}
  ]
}
```

而不是巢狀節點物件，例如：

```
UNWIND $nodes as n
CREATE(t:ticket n.properties)
```

使用參數：

```
parameters={
  "nodes": [
    {"id": "ticket1", "properties": {"title": "food poisoning", "severity": "2"}},
    {"id": "ticket2", "properties": {"title": "Simone is in office", "severity": "3"}}
  ]
}
```

# 在可變長度路徑 (VLP) 表達式中，將更嚴格的節點放在左側
可變長度路徑 (VLP) 表達式

 在可變長度路徑 (VLP) 查詢中，查詢引擎會透過選擇在表達式的左側或右側啟動周遊來最佳化評估。決策是根據左側和右側模式的基數。基數是符合指定模式的節點數量。
+  如果右側模式的基數為 1，則右側將是起點。
+  如果左側和右側的基數為 1，則會檢查兩側的擴展，並以較小的擴展從側面開始。擴展是 VLP 表達式左側節點和右側節點的傳出或傳入邊緣數量。只有在 VLP 關係為單向且提供關係類型時，才會使用最佳化的此部分。
+  否則，左側將是起點。

 對於 VLP 表達式鏈，此最佳化只能套用至第一個表達式。從左側開始評估其他 VLPs。例如，讓 (a)、(b) 的基數為 1，而 (c) 的基數大於 1。
+  `(a)-[*1..]->(c)`：評估開頭為 (a)。
+  `(c)-[*1..]->(a)`：評估開頭為 (a)。
+  `(a)-[*1..]-(c)`：評估開頭為 (a)。
+  `(c)-[*1..]-(a)`：評估開頭為 (a)。

 現在讓 (a) 的傳入邊緣為兩個，(a) 的傳出邊緣為三個，(b) 的傳入邊緣為四個，(b) 的傳出邊緣為五個。
+  `(a)-[*1..]->(b)`：評估以 (a) 開頭，因為 (a) 的傳出邊緣小於 (b) 的傳入邊緣。
+  `(a)<-[*1..]-(b)`：評估以 (a) 開頭，因為 (a) 的傳入邊緣小於 (b) 的傳出邊緣。

 一般而言，請將更嚴格的模式放在 VLP 表達式的左側。

# 使用精細的關係名稱避免備援節點標籤檢查
使用精細的關係名稱

 最佳化效能時，使用節點模式獨有的關係標籤可移除節點上的標籤篩選。考慮圖形模型，其中關係`likes`僅用於定義兩個`person`節點之間的關係。我們可以撰寫下列查詢來尋找此模式：

```
MATCH (n:person)-[:likes]->(m:person)
RETURN n, m
```

 n 和 m 上的`person`標籤檢查是多餘的，因為我們定義了與 的關係只會在兩者都屬於類型 時出現`person`。若要最佳化效能，我們可以撰寫查詢，如下所示：

```
MATCH (n)-[:likes]->(m)
RETURN n, m
```

 當屬性專屬於單一節點標籤時，此模式也可以套用。假設只有`person`節點具有 屬性 `email`，因此驗證節點標籤相符項目`person`是多餘的。將此查詢寫入為：

```
MATCH (n:person)
WHERE n.email = 'xxx@gmail.com'
RETURN n
```

 效率低於將此查詢寫入為：

```
MATCH (n)
WHERE n.email = 'xxx@gmail.com'
RETURN n
```

 只有在效能很重要，而且您已在建模程序中進行檢查，以確保這些邊緣標籤不會重複使用於涉及其他節點標籤的模式時，才應該採用此模式。如果您稍後在其他節點標籤上引入 `email` 屬性，例如 `company`，則這兩個版本的查詢結果會有所不同。

# 盡可能指定邊緣標籤
指定邊緣標籤

 在模式中指定邊緣時，建議盡可能提供邊緣標籤。請考慮下列範例查詢，此查詢用於連結住在城市的所有人員與造訪該城市的所有人員。

```
MATCH (person)-->(city {country: "US"})-->(anotherPerson)
RETURN person, anotherPerson
```

 如果您的圖形模型使用多個邊緣標籤將人員連結至城市以外的節點，則透過不指定最終標籤，Neptune 將需要評估稍後將捨棄的其他路徑。在上述查詢中，由於未提供邊緣標籤，因此引擎會先執行更多工作，然後篩選掉值以取得正確的結果。上述查詢的較好版本可能是：

```
MATCH (person)-[:livesIn]->(city {country: "US"})-[:visitedBy]->(anotherPerson)
RETURN person, anotherPerson
```

 這不僅有助於評估，還可以讓查詢規劃器建立更好的計劃。您甚至可以結合此最佳實務與備援節點標籤檢查，以移除城市標籤檢查，並將查詢寫入為：

```
MATCH (person)-[:livesIn]->({country: "US"})-[:visitedBy]->(anotherPerson)
RETURN person, anotherPerson
```

# 盡可能避免使用 WITH 子句
避免 WITH 子句

 openCypher 中的 WITH 子句充當執行所有項目前的邊界，然後將產生的值傳遞至查詢的剩餘部分。當您需要臨時彙總或想要限制結果數量時，除了應該避免使用 WITH 子句之外，還需要 WITH 子句。一般指引是移除這些簡單的 WITH 子句 （不彙總、排序或限制），讓查詢規劃器能夠處理整個查詢，以建立全域最佳計劃。例如，假設您撰寫查詢來傳回所有住在 的人`India`：

```
MATCH (person)-[:lives_in]->(city)
WITH person, city
MATCH (city)-[:part_of]->(country {name: 'India'})
RETURN collect(person) AS result
```

 在上述版本中， WITH 子句會限制在 之前放置模式 `(city)-[:part_of]->(country {name: 'India'})`（較嚴格）`(person)-[:lives_in]->(city)`。這會使計劃次佳。此查詢的最佳化是移除 WITH 子句，並讓規劃器運算最佳計劃。

```
MATCH (person)-[:lives_in]->(city)
MATCH (city)-[:part_of]->(country {name: 'India'})
RETURN collect(person) AS result
```

# 儘早在查詢中放置限制性篩選條件
限制性篩選條件

 在所有案例中，在查詢中提早放置篩選條件有助於減少查詢計劃必須考慮的中繼解決方案。這表示執行查詢所需的記憶體和運算資源較少。

 下列範例可協助您了解這些影響。假設您撰寫查詢來傳回所有住在 的人員`India`。查詢的一個版本可以是：

```
MATCH (n)-[:lives_in]->(city)-[:part_of]->(country)
WITH country, collect(n.firstName + " "  + n.lastName) AS result
WHERE country.name = 'India'
RETURN result
```

 上述版本的查詢不是實現此使用案例的最佳方式。篩選條件稍後`country.name = 'India'`會出現在查詢模式中。它會先收集所有人員及其居住地，並依國家/地區分組，然後僅篩選 的 群組`country.name = India`。僅查詢居住者`India`，然後執行收集彙總的最佳方式。

```
MATCH (n)-[:lives_in]->(city)-[:part_of]->(country)
WHERE country.name = 'India'
RETURN collect(n.firstName + " "  + n.lastName) AS result
```

 一般規則是在引入變數後盡快放置篩選條件。

# 明確檢查屬性是否存在
明確檢查屬性

 根據 openCypher 語意，當存取屬性時，它等同於選用聯結，即使屬性不存在，也必須保留所有資料列。如果您根據圖形結構描述知道該實體一律存在特定屬性，請明確檢查該屬性是否存在可讓查詢引擎建立最佳計劃並改善效能。

 請考慮圖形模型，其中 類型的節點`person`一律具有屬性 `name`。而不是這樣做：

```
MATCH (n:person)
RETURN n.name
```

 使用 IS NOT NULL 檢查明確驗證查詢中存在的屬性：

```
MATCH (n:person)
WHERE n.name IS NOT NULL
RETURN n.name
```

# 請勿使用具名路徑 （除非必要）
避免具名路徑

 查詢中的具名路徑一律需支付額外費用，這可能會增加延遲和記憶體用量方面的懲罰。請考處下列查詢：

```
MATCH p = (n)-[:commentedOn]->(m)
WITH p, m, n, n.score + m.score as total
WHERE total > 100 
MATCH (m)-[:commentedON]->(o)
WITH p, m, n, distinct(o) as o1
RETURN p, m.name, n.name, o1.name
```

 在上述查詢中，假設我們只想知道節點的屬性，則不需要使用路徑「p」。透過將具名路徑指定為變數，使用 DISTINCT 的彙總操作在時間和記憶體用量方面都會變得昂貴。上述查詢的更最佳化版本可能是：

```
MATCH (n)-[:commentedOn]->(m)
WITH m, n, n.score + m.score as total
WHERE total > 100 
MATCH (m)-[:commentedON]->(o)
WITH m, n, distinct(o) as o1
RETURN m.name, n.name, o1.name
```

# 避免 COLLECT(DISTINCT())


**注意**  
從引擎 [ 1.4.7.0](engine-releases-1.4.7.0.md) 版開始，不再需要此建議的重寫。

 每當要形成包含不同值的清單時，都會使用 COLLECT(DISTINCT())。COLLECT 是一種彙總函數，分組是根據在相同陳述式中投影的其他金鑰來完成。使用差異時，輸入會分割成多個區塊，其中每個區塊代表一個群組進行減少。隨著群組數量的增加，效能將會受到影響。在 Neptune 中，在實際收集/形成清單之前執行 DISTINCT 會更有效率。這允許對整個區塊的分組索引鍵直接進行分組。

 請考處下列查詢：

```
MATCH (n:Person)-[:commented_on]->(p:Post)
WITH n, collect(distinct(p.post_id)) as post_list
RETURN n, post_list
```

 撰寫此查詢的另一種最佳方式是：

```
MATCH (n:Person)-[:commented_on]->(p:Post)
WITH DISTINCT n, p.post_id as postId
WITH n, collect(postId) as post_list
RETURN n, post_list
```

# 擷取所有屬性值時，偏好個別屬性查詢的屬性函數
偏好 屬性函數

 `properties()` 函數用於傳回包含實體所有屬性的映射，而且比個別傳回屬性更有效率。

 假設您的`Person`節點包含 5 個屬性：`firstName`、`dept`、、 `lastName` `age`和 `company`，則最好使用下列查詢：

```
MATCH (n:Person)
WHERE n.dept = 'AWS'
RETURN properties(n) as personDetails
```

 而不是使用：

```
MATCH (n:Person)
WHERE n.dept = 'AWS'
RETURN n.firstName, n.lastName, n.age, n.dept, n.company
    
=== OR ===
    
MATCH (n:Person)
WHERE n.dept = 'AWS'
RETURN {firstName: n.firstName, lastName: n.lastName, age: n.age, 
department: n.dept, company: n.company} as personDetails
```

# 在查詢之外執行靜態運算
靜態運算

 建議在用戶端解析靜態運算 （簡單的數學/字串操作）。請考慮此範例，其中您想要尋找比作者長一年或更短的所有人：

```
MATCH (m:Message)-[:HAS_CREATOR]->(p:person)
WHERE p.age <= ($age + 1)
RETURN m
```

 在這裡， 會透過參數`$age`注入查詢，然後新增至固定值。然後，將此值與 進行比較`p.age`。反之，更好的方法是在用戶端進行新增，並將計算值作為參數 \$1ageplusone 傳遞。這有助於查詢引擎建立最佳化計劃，並避免每個傳入資料列的靜態運算。遵循這些準則，更有效率的查詢會是：

```
MATCH (m:Message)-[:HAS_CREATOR]->(p:person)
WHERE p.age <= $ageplusone
RETURN m
```

# 使用 UNWIND 而非個別陳述式的批次輸入
批次處理輸入

 每當需要對不同的輸入執行相同的查詢時，而不是對每個輸入執行一個查詢，對一批輸入執行查詢會更高效能。

 如果您想要在一組節點上合併，其中一個選項是執行每個輸入的合併查詢：

```
MERGE (n:Person {`~id`: $id})
SET n.name = $name, n.age = $age, n.employer = $employer
```

 使用參數：

```
params = {id: '1', name: 'john', age: 25, employer: 'Amazon'}
```

 每個輸入都需要執行上述查詢。雖然此方法有效，但可能需要對大量輸入執行許多查詢。在此案例中，批次處理可能有助於減少在伺服器上執行的查詢數量，並改善整體輸送量。

 使用下列模式：

```
UNWIND $persons as person
MERGE (n:Person {`~id`: person.id})
SET n += person
```

 使用參數：

```
params = {persons: [{id: '1', name: 'john', age: 25, employer: 'Amazon'}, 
{id: '2', name: 'jack', age: 28, employer: 'Amazon'},
{id: '3', name: 'alice', age: 24, employer: 'Amazon'}...]}
```

 建議使用不同的批次大小進行實驗，以判斷哪些項目最適合您的工作負載。

# 偏好使用節點/關係IDs
偏好自訂 IDs

 Neptune 允許使用者在節點和關係上明確指派 IDs。ID 在資料集中必須是全域唯一的，並且確定性才有用。決定性 ID 可以用作查詢或篩選機制，就像屬性一樣；但是，使用 ID 從查詢執行角度比使用屬性更最佳化。使用自訂 IDs 有幾個好處 - 
+  現有實體的屬性可以是 null，但 ID 必須存在。這可讓查詢引擎在執行期間使用最佳化聯結。
+  執行並行變動查詢時，當 IDs用於存取節點時，並[行修改例外](https://docs.aws.amazon.com//neptune/latest/userguide/transactions-exceptions.html)狀況 (CMEs) 的機率會大幅降低，因為 IDs 的鎖定數量會比屬性來得少，因為其強制執行的唯一性。
+  使用 IDs 可避免建立重複資料的機會，因為 Neptune 會在 IDs上強制執行唯一性，與屬性不同。

 下列查詢範例使用自訂 ID：

**注意**  
 屬性`~id`用於指定 ID，而 `id`僅儲存為任何其他屬性。

```
CREATE (n:Person {`~id`: '1', name: 'alice'})
```

 不使用自訂 ID：

```
CREATE (n:Person {id: '1', name: 'alice'})
```

 如果使用後者機制，則沒有唯一性強制執行，您可以稍後執行查詢：

```
CREATE (n:Person {id: '1', name: 'john'})
```

 這會建立`id=1`名為 的第二個節點`john`。在此案例中，您現在將有兩個具有 的節點`id=1`，每個節點都有不同的名稱 - (alice 和 john)。

# 避免在查詢中進行 \$1id 運算
避免 \$1id 運算

 在查詢中使用自訂 IDs 時，請一律在查詢之外執行靜態運算，並在參數中提供這些值。提供靜態值時，引擎更能夠最佳化查詢，並避免掃描和篩選這些值。

 如果您想要在資料庫中現有的節點之間建立邊緣，其中一個選項可能是：

```
UNWIND $sections as section
MATCH (s:Section {`~id`: 'Sec-' + section.id})
MERGE (s)-[:IS_PART_OF]->(g:Group {`~id`: 'g1'})
```

 使用參數：

```
parameters={sections: [{id: '1'}, {id: '2'}]}
```

 在上述查詢中，正在查詢中計算 區段`id`的 。由於運算是動態的，因此引擎無法靜態內嵌 ID，最終會掃描所有區段節點。然後，引擎會針對所需的節點執行後篩選。如果資料庫中有許多區段節點，這可能會很昂貴。

 達成此目的更好的方法是在要傳遞至資料庫的 ID 前面`Sec-`加上 ：

```
UNWIND $sections as section
MATCH (s:Section {`~id`: section.id})
MERGE (s)-[:IS_PART_OF]->(g:Group {`~id`: 'g1'})
```

 使用參數：

```
parameters={sections: [{id: 'Sec-1'}, {id: 'Sec-2'}]}
```

# 更新/合併多個節點


 在多個節點上執行 `MERGE`或 `CREATE`查詢時，建議將 與單一 MERGE/CREATE 子句`UNWIND`搭配使用，而不是對每個節點使用 MERGE/CREATE 子句。由於每行需要最佳化，針對一個節點使用一個子句的查詢會導致執行計畫效率低下。這會導致查詢的大部分執行時間花費在靜態處理，而不是實際更新。

 每個節點有一個子句不是最佳的，因為它不會隨著節點數量的增加而擴展：

```
MERGE (p1:Person {name: 'NameA'})
ON CREATE SET p1 += {prop1: 'prop1V1', prop2: 'prop2V1'}
MERGE (p2:Person {name: 'NameB'})
ON CREATE SET p2 += {prop1: 'prop1V2', prop2: 'prop2V2'}
MERGE (p3:Person {name: 'NameC'})
ON CREATE SET p3 += {prop1: 'prop1V3', prop2: 'prop1V3'}
```

 `UNWIND` 搭配一個 MERGE/CREATE 子句使用 可允許相同行為，但執行計畫更理想。考慮到這一點，變更後的查詢會如下所示：

```
## If not using custom id for nodes/relationship
UNWIND [{name: 'NameA', prop1: 'prop1V1', prop2: 'prop2V1'}, {name: 'NameB', prop1: 'prop1V2', prop2: 'prop2V2'}, {name: 'NameC', prop1: 'prop1V3', prop2: 'prop1V3'}] AS props
MERGE (p:Person {name: props.name})
ON CREATE SET p = props

## If using custom id for nodes/relationship
UNWIND [{`~id`: '1', 'name': 'NameA', 'prop1: 'prop1V1', prop2: 'prop2V1'}, {`~id`: '2', name: 'NameB', prop1: 'prop1V2', prop2: 'prop2V2'}, {`~id`: '3', name: 'NameC', prop1: 'prop1V3', prop2: 'prop1V3'}] AS props
MERGE (p:Person {`~id`: props.id})
ON CREATE SET p = removeKeyFromMap(props, '~id')
```