本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。
Go 的 Amazon QLDB 驅動程序-食譜參考
重要
支援結束通知:現有客戶將能夠使用 Amazon,QLDB直到 2025 年 7 月 31 日終止支援為止。有關更多詳細信息,請參閱將 Amazon QLDB 分類帳遷移到 Amazon Aurora 郵政. SQL
本參考指南顯示適用於 Go 的 Amazon QLDB 驅動程式的常見使用案例。它提供了 Go 代碼示例,演示如何使用驅動程序運行基本的創建,讀取,更新和刪除(CRUD)操作。它還包括用於處理 Amazon Ion 資料的程式碼範例。此外,本指南還重點介紹了使交易冪等和實施唯一性約束的最佳實踐。
注意
在適用的情況下,某些用例針對 Go QLDB 驅動程序的每個受支持的主要版本具有不同的代碼示例。
內容
匯入驅動程式
下列程式碼範例會匯入驅動程式和其他必要的 AWS 套件。
注意
此範例也會匯入 Amazon Ion 套件 (amzn/ion-go/ion
)。在此參考中執行某些資料作業時,您需要此套件來處理 Ion 資料。如需進一步了解,請參閱與 Amazon 離子工作。
實例化驅動程序
下列程式碼範例會建立連線至指定分類帳名稱中之指定分類帳名稱的驅動程式執行環境 AWS 區域。
CRUD操作
QLDB在交易中執行建立、讀取、更新和刪除 (CRUD) 作業。
警告
作為最佳實踐,使您的寫入事務嚴格為冪等。
使交易冪等
我們建議您將寫入事務設為冪等,以避免在重試的情況下出現任何意外的副作用。如果事務可以運行多次並每次產生相同的結果,則事務是冪等的。
例如,假設將文件插入名為的資料表中的交易Person
。事務應該首先檢查文檔是否已經存在於表中。如果沒有此檢查,表格最終可能會出現重複的文件。
假設成QLDB功地在服務器端提交事務,但客戶端在等待響應時超時。如果事務不是冪等的,則在重試的情況下,可以多次插入相同的文檔。
使用索引來避免全表掃描
我們也建議您在索引欄位或文件 ID 上使用相等運算子來執行具有述WHERE
詞子句的陳述式;例如,WHERE indexedField = 123
或WHERE indexedField IN (456, 789)
。如果沒有此索引查找,則QLDB需要進行表掃描,這可能會導致事務超時或樂觀的並發控制(OCC)衝突。
如需有關 OCC 的詳細資訊,請參閱 Amazon QLDB 并发模型。
隱含建立的交易
QLDBDriver.ExecuteTransaction
封裝隱含建立之交易的執行個體。
您可以使用函數在 lambda 函數中執行陳述式。Transaction.Execute
當 lambda 函數返回時,驅動程序隱式地提交交易。
下列各節說明如何執行基本CRUD作業、指定自訂重試邏輯,以及實作唯一性條件約束。
建立資料表
result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("CREATE TABLE Person") })
建立索引
result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("CREATE INDEX ON Person(GovId)") })
閱讀文件
var decodedResult map[string]interface{} // Assumes that Person table has documents as follows: // { "GovId": "TOYENC486FH", "FirstName": "Brent" } _, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { result, err := txn.Execute("SELECT * FROM Person WHERE GovId = 'TOYENC486FH'") if err != nil { return nil, err } for result.Next(txn) { ionBinary := result.GetCurrentData() err = ion.Unmarshal(ionBinary, &decodedResult) if err != nil { return nil, err } fmt.Println(decodedResult) // prints map[GovId: TOYENC486FH FirstName:Brent] } if result.Err() != nil { return nil, result.Err() } return nil, nil }) if err != nil { panic(err) }
使用查詢參數
下列程式碼範例會使用原生型別查詢參數。
result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("SELECT * FROM Person WHERE GovId = ?", "TOYENC486FH") }) if err != nil { panic(err) }
下列程式碼範例會使用多個查詢參數。
result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("SELECT * FROM Person WHERE GovId = ? AND FirstName = ?", "TOYENC486FH", "Brent") }) if err != nil { panic(err) }
下列程式碼範例使用查詢參數清單。
govIDs := []string{}{"TOYENC486FH", "ROEE1", "YH844"} result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("SELECT * FROM Person WHERE GovId IN (?,?,?)", govIDs...) }) if err != nil { panic(err) }
注意
當您在沒有索引查閱的情況下執行查詢時,會叫用完整資料表掃描。在此範例中,我們建議在GovId
欄位上建立索引以最佳化效能。如果沒有開啟索引GovId
,查詢可能會有更多延遲,也可能導致OCC衝突例外狀況或交易逾時。
插入文件
下列程式碼範例會插入原生資料類型。
_, err = driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { // Check if a document with a GovId of TOYENC486FH exists // This is critical to make this transaction idempotent result, err := txn.Execute("SELECT * FROM Person WHERE GovId = ?", "TOYENC486FH") if err != nil { return nil, err } // Check if there are any results if result.Next(txn) { // Document already exists, no need to insert } else { person := map[string]interface{}{ "GovId": "TOYENC486FH", "FirstName": "Brent", } _, err = txn.Execute("INSERT INTO Person ?", person) if err != nil { return nil, err } } return nil, nil })
此事務將一個文檔插入到Person
表中。在插入之前,它會先檢查文件是否已存在於表格中。這個檢查使事務本質上是冪等的。即使您多次運行此事務,也不會造成任何意外的副作用。
注意
在此範例中,我們建議在GovId
欄位上建立索引以最佳化效能。如果沒有開啟索引GovId
,陳述式可能會有更多延遲,也可能導致OCC衝突例外狀況或交易逾時。
在一個語句中插入多個文檔
要通過使用單個INSERT語句插入多個文檔,你可以通過類型列表的參數到語句如下。
// people is a list txn.Execute("INSERT INTO People ?", people)
傳遞清單時,您不會將變數預留位置 (?
) 括在雙尖括號 (<<...>>
) 中。在手動 PartiQL 陳述式中,雙角括號表示稱為袋子的無序集合。
更新文件
下列程式碼範例會使用原生資料類型。
result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("UPDATE Person SET FirstName = ? WHERE GovId = ?", "John", "TOYENC486FH") })
注意
在此範例中,我們建議在GovId
欄位上建立索引以最佳化效能。如果沒有開啟索引GovId
,陳述式可能會有更多延遲,也可能導致OCC衝突例外狀況或交易逾時。
刪除文件
下列程式碼範例會使用原生資料類型。
result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("DELETE FROM Person WHERE GovId = ?", "TOYENC486FH") })
注意
在此範例中,我們建議在GovId
欄位上建立索引以最佳化效能。如果沒有開啟索引GovId
,陳述式可能會有更多延遲,也可能導致OCC衝突例外狀況或交易逾時。
在交易中執行多個陳述式
// This code snippet is intentionally trivial. In reality you wouldn't do this because you'd // set your UPDATE to filter on vin and insured, and check if you updated something or not. func InsureCar(driver *qldbdriver.QLDBDriver, vin string) (bool, error) { insured, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { result, err := txn.Execute( "SELECT insured FROM Vehicles WHERE vin = ? AND insured = FALSE", vin) if err != nil { return false, err } hasNext := result.Next(txn) if !hasNext && result.Err() != nil { return false, result.Err() } if hasNext { _, err = txn.Execute( "UPDATE Vehicles SET insured = TRUE WHERE vin = ?", vin) if err != nil { return false, err } return true, nil } return false, nil }) if err != nil { panic(err) } return insured.(bool), err }
重試邏輯
驅動程序的Execute
函數具有內置的重試機制,如果發生可重試的異常(例如超時或衝突),則會重試事務。OCC重試嘗試次數上限和輪詢策略是可設定的。
預設重試限制為4
,且預設的輪詢策略ExponentialBackoffStrategy10
毫秒為基礎。您可以使用的執行個體來設定每個驅動程式執行個體以及每個交易的重試原則RetryPolicy
下列程式碼範例會指定具有自訂重試限制的重試邏輯,以及驅動程式執行個體的自訂輪詢策略。
import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/qldbsession" "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver" ) func main() { awsSession := session.Must(session.NewSession(aws.NewConfig().WithRegion("
us-east-1
"))) qldbSession := qldbsession.New(awsSession) // Configuring retry limit to 2 retryPolicy := qldbdriver.RetryPolicy{MaxRetryLimit: 2} driver, err := qldbdriver.New("test-ledger", qldbSession, func(options *qldbdriver.DriverOptions) { options.RetryPolicy = retryPolicy }) if err != nil { panic(err) } // Configuring an exponential backoff strategy with base of 20 milliseconds retryPolicy = qldbdriver.RetryPolicy{ MaxRetryLimit: 2, Backoff: qldbdriver.ExponentialBackoffStrategy{SleepBase: 20, SleepCap: 4000, }} driver, err = qldbdriver.New("test-ledger", qldbSession, func(options *qldbdriver.DriverOptions) { options.RetryPolicy = retryPolicy }) if err != nil { panic(err) } }
下列程式碼範例會指定具有自訂重試限制的重試邏輯,以及特定匿名函式的自訂輪詢策略。此SetRetryPolicy
函數會覆寫為驅動程式執行個體設定的重試原則。
import ( "context" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/qldbsession" "github.com/awslabs/amazon-qldb-driver-go/v2/qldbdriver" ) func main() { awsSession := session.Must(session.NewSession(aws.NewConfig().WithRegion("
us-east-1
"))) qldbSession := qldbsession.New(awsSession) // Configuring retry limit to 2 retryPolicy1 := qldbdriver.RetryPolicy{MaxRetryLimit: 2} driver, err := qldbdriver.New("test-ledger", qldbSession, func(options *qldbdriver.DriverOptions) { options.RetryPolicy = retryPolicy1 }) if err != nil { panic(err) } // Configuring an exponential backoff strategy with base of 20 milliseconds retryPolicy2 := qldbdriver.RetryPolicy{ MaxRetryLimit: 2, Backoff: qldbdriver.ExponentialBackoffStrategy{SleepBase: 20, SleepCap: 4000, }} // Overrides the retry policy set by the driver instance driver.SetRetryPolicy(retryPolicy2) driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { return txn.Execute("CREATE TABLE Person") }) }
實施唯一性約束
QLDB不支持唯一索引,但您可以在應用程序中實現此行為。
假設您要在Person
表中的GovId
字段上實現唯一性約束。要做到這一點,你可以寫一個事務,執行以下操作:
-
斷言該表沒有具有指定
GovId
的現有文檔。 -
插入文檔,如果斷言通過。
如果競爭交易同時通過斷言,則只有其中一個交易將成功提交。另一個交易會失敗,並出現OCC衝突例外狀況。
下列程式碼範例會示範如何實作此唯一性條件約束邏輯。
govID := "TOYENC486FH" document := map[string]interface{}{ "GovId": "TOYENC486FH", "FirstName": "Brent", } result, err := driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) { // Check if doc with GovId = govID exists result, err := txn.Execute("SELECT * FROM Person WHERE GovId = ?", govID) if err != nil { return nil, err } // Check if there are any results if result.Next(txn) { // Document already exists, no need to insert return nil, nil } return txn.Execute("INSERT INTO Person ?", document) }) if err != nil { panic(err) }
注意
在此範例中,我們建議在GovId
欄位上建立索引以最佳化效能。如果沒有開啟索引GovId
,陳述式可能會有更多延遲,也可能導致OCC衝突例外狀況或交易逾時。
與 Amazon 離子工作
以下各節說明如何使用 Amazon Ion 模組來處理離子資料。
匯入離子模組
import "github.com/amzn/ion-go/ion"
建立離子類型
Go 的 Ion 庫目前不支持文檔對象模型(DOM),因此您無法創建 Ion 數據類型。但是您可以在使用 Go 本機類型和 Ion 二進製文件之間進行編組和解組。QLDB
獲取離子二進制
aDict := map[string]interface{}{ "GovId": "TOYENC486FH", "FirstName": "Brent", } ionBytes, err := ion.MarshalBinary(aDict) if err != nil { panic(err) } fmt.Println(ionBytes) // prints [224 1 0 234 238 151 129 131 222 147 135 190 144 133 71 111 118 73 100 137 70 105 114 115 116 78 97 109 101 222 148 138 139 84 79 89 69 78 67 52 56 54 70 72 139 133 66 114 101 110 116]
取得離子文字
aDict := map[string]interface{}{ "GovId": "TOYENC486FH", "FirstName": "Brent", } ionBytes, err := ion.MarshalText(aDict) if err != nil { panic(err) } fmt.Println(string(ionBytes)) // prints {FirstName:"Brent",GovId:"TOYENC486FH"}
如需 Ion 的詳細資訊,請參閱上的 Amazon Ion 文件