實體、屬性和金鑰

Datastore 中的資料物件稱為「實體」。實體有一或多個命名的「屬性」,每個屬性可以有一或多個值。相同種類的實體不需要具有相同屬性,而實體的特定屬性值並不全都需要屬於相同的資料類型 (如有必要,應用程式可在本身的資料模型建立及強制執行這類限制)。

Datastore 支援各種屬性值的資料類型。其中包括:

  • 整數
  • 浮點數
  • 字串
  • 日期
  • 二進位資料

如需這些類型的完整清單,請參閱屬性和值類型一節。

Datastore 中的每個實體都有專門用來識別該實體的「金鑰」。索引鍵由下列元件組成:

  • 實體的「命名空間」,可允許多租戶架構
  • 實體的類型,可將實體分類以便進行 Datastore 查詢
  • 個別實體的ID,可以是以下兩者之一:
    • 「索引鍵名稱」字串
    • 整數「數字 ID」
  • 選用的祖系路徑,可將實體置於 Datastore 階層之中

應用程式可使用實體的索引鍵從 Datastore 擷取個別實體,或依據實體的索引鍵或屬性值發出查詢,以擷取一或多個實體。

Go App Engine SDK 包含套件,可將 Datastore 實體表示為 Go 結構體,並在 Datastore 中儲存及擷取這些實體。

Datastore 本身不會對實體結構強制執行任何限制,例如特定屬性是否具有特定類型的值;這項工作是由應用程式負責。

種類及 ID

每個 Datastore 實體都屬於特定「種類」,可將實體分門別類,方便查詢。舉例來說,人力資源應用程式可以使用屬於 Employee 種類的實體來代表公司的每位員工。在 Go Datastore API 中,您可以在建立 datastore.Key 時指定實體的類型。以兩條底線 (__) 開頭的種類名稱均為保留名稱,不得使用。

以下範例示範了如何建立 Employee 種類的實體、填入屬性值,並將其儲存至 Datastore:

import (
	"context"
	"time"

	"google.golang.org/appengine/datastore"
)

type Employee struct {
	FirstName          string
	LastName           string
	HireDate           time.Time
	AttendedHRTraining bool
}

func f(ctx context.Context) {
	// ...
	employee := &Employee{
		FirstName: "Antonio",
		LastName:  "Salieri",
		HireDate:  time.Now(),
	}
	employee.AttendedHRTraining = true

	key := datastore.NewIncompleteKey(ctx, "Employee", nil)
	if _, err := datastore.Put(ctx, key, employee); err != nil {
		// Handle err
	}
	// ...
}

Employee 類型會為資料模型宣告四個欄位:FirstNameLastNameHireDateAttendedHRTraining

除了種類以外,建立每個實體時還會指派實體的「ID」。因為 ID 屬於實體索引鍵的一部分,所以會與實體永久關聯,且無法變更。ID 可利用兩種方式指派:

  • 您的應用程式可以指定自身的實體「索引鍵名稱」字串。
  • 您可以讓 Datastore 自動指派整數的「數字 ID」給實體。

如要指派鍵名稱給實體,請將非空白的 stringID 引數提供至 datastore.NewKey

// Create a key with a key name "asalieri".
key := datastore.NewKey(
	ctx,        // context.Context
	"Employee", // Kind
	"asalieri", // String ID; empty means no string ID
	0,          // Integer ID; if 0, generate automatically. Ignored if string ID specified.
	nil,        // Parent Key; nil means no parent
)

如要讓 Datastore 自動指派數字 ID,請使用空白的 stringID 引數:

// Create a key such as Employee:8261.
key := datastore.NewKey(ctx, "Employee", "", 0, nil)
// This is equivalent:
key = datastore.NewIncompleteKey(ctx, "Employee", nil)

指派 ID

Datastore 可使用兩種不同的自動 ID 政策設定自動產生 ID:

  • default 政策可產生隨機順序的未使用 ID,近乎均勻分佈。每個 ID 最長可達 16 個十進位數字。
  • legacy 政策可建立一系列非連續的較小整數 ID。

如果您要向使用者顯示實體 ID,並/或依據其順序顯示,最理想的方式就是使用手動分配。

Datastore 會產生順序隨機且近乎均勻分佈的未使用 ID。每個 ID 最長可達 16 個十進位數字。

祖系路徑

Cloud Datastore 中的實體會形成階層結構空間,與檔案系統的目錄結構相似。建立實體時,可選擇將其他實體指定為「父項」;新實體則為父系實體的「子項」(請注意,不同於檔案系統,父項實體不需要實際存在)。沒有父項的實體則是「根實體」。實體與父項實體之間具有永久關聯性,一旦實體建立後就無法變更。Cloud Datastore 絕對不會將相同的數字 ID 指派給父項相同的兩個實體,也不會指派給兩個根實體 (即沒有父項的實體)。

實體的父項、父項的父項等以此類推,全都是這個實體的「祖系」;實體的子項、子項的子項等等,則都是其「子系」。根實體及其所有子系都屬於相同的「實體群組」。從根實體開始,再從父項到子項,最後到指定實體的實體序列,即構成該實體的「祖系路徑」。識別實體的完整索引鍵,包含連串種類-ID 組合序列,其中指定了實體的祖系路徑,最後則以該實體本身的種類-ID 組合做為結尾:

[Person:GreatGrandpa, Person:Grandpa, Person:Dad, Person:Me]

根實體的祖系路徑是空白路徑,其金鑰只包含實體本身的種類與 ID。

[Person:GreatGrandpa]

本概念以下圖說明:

顯示實體群組中根實體與子實體的關係

如要指定實體的父項,請將 parent 引數用於 datastore.NewKey。此指數值應為父項實體金鑰。以下範例會建立 Address 種類的實體,並指定 Employee 實體做為其父項:

// Create Employee entity
employee := &Employee{ /* ... */ }
employeeKey, err := datastore.Put(ctx, datastore.NewIncompleteKey(ctx, "Employee", nil), employee)

// Use Employee as Address entity's parent
// and save Address entity to datastore
address := &Address{ /* ... */ }
addressKey := datastore.NewIncompleteKey(ctx, "Address", employeeKey)
_, err = datastore.Put(ctx, addressKey, address)

交易和實體群組

每次嘗試建立、更新或刪除實體時,都是在交易的背景下進行。單一交易可包含的前述作業數量不受限制。為了維持資料一致性,交易一定會使其中包含的變動整組適用於 Datastore,其中如有任何變動失敗,則整組變動均不適用。此外,在相同交易內執行的所有同步一致性讀取作業 (祖系查詢或「get」) 將出現一致的資料快照。

如前所述,實體群組是一組實體,透過祖系連接至共同根元素。將資料組織為實體群組,可限制執行的交易:

  • 一項交易所存取的所有資料,最多只能存在於 25 個實體群組中。
  • 如果您想在交易中使用查詢,必須將資料歸納成實體群組,才可指定能夠比對出正確資料的祖系篩選條件。
  • 單一實體群組的寫入總處理量大約是每秒一次交易。之所以會有這項限制,原因在於 Datastore 會針對橫跨大範圍地理區域的每個實體執行免主機的同步複製作業,以利發揮高度的可靠性和容錯能力。

在許多應用程式中,當您想取得不相關資料的廣泛檢視畫面時,可以使用最終一致性 (即跨多個實體群組的非祖系查詢,有時可能會傳回稍微過時的資料),然後在查看或編輯一組高度相關資料時,使用同步一致性 (祖系查詢或單一實體的 get)。在此類應用程式中,針對各組高度相關的資料使用獨立實體群組,通常會有不錯的效果。詳情請參閱「建立同步一致性結構」。

屬性和值類型

與實體相關聯的資料值由一或多個「屬性」組成。每個屬性都有一個名稱及一或多個值。一個屬性可能會有多個類型的值,而兩個實體的相同屬性可能會有不同類型的值。屬性可能已建立索引或未建立索引 (排序或篩選屬性「P」的查詢將忽略「P」未建立索引的實體)。一個實體最多可有 20,000 個已建立索引的屬性。

支援的值類型如下:

值類型 Go 類型 排序順序 附註
整數 int
int8
int16
int32
int64
數字 64 位元整數,帶正負號
浮點數 float32
float64
數字 64 位元雙精度,
IEEE 754
布林值 bool false<true
字串 (短) string Unicode
最多 1500 個位元組。超過 1500 位元組的值會在執行階段造成錯誤。
字串 (長) string (含 noindex) 最多 1 MB

不會建立索引
位元組切割 (短) datastore.ByteString 位元組順序 最多 1500 個位元組。超過 1500 位元組的值會在執行階段造成錯誤。
位元組切割 (長) []byte 最多 1 MB

不會建立索引
日期與時間 time.Time 依時間順序
地理點 appengine.GeoPoint 依照緯度、
然後經度
Datastore 索引鍵 *datastore.Key 依路徑元素
(種類、ID、
種類、ID...)
Blobstore 金鑰 appengine.BlobKey 位元組順序

您也可以使用 structslice 匯總屬性。詳情請參閱 Datastore 參考資料

當查詢的屬性具有混合類型的值時,Datastore 會根據內部表示法使用確定性排序:

  1. 空值
  2. 固定點數
    • 整數
    • 日期和時間
  3. 布林值
  4. 位元組序列
    • 位元組切割 (短)
    • Unicode 字串
    • Blobstore 索引鍵
  5. 浮點數
  6. 地理點
  7. Datastore 索引鍵

由於長位元組切割和長字串不會建立索引,因此未定義排序。

處理實體

應用程式可使用 Datastore API 建立、擷取、更新及刪除實體。如果應用程式知道實體的完整索引鍵 (或是可從父系索引鍵、種類和 ID 中取得),即可在該實體上使用索引鍵直接作業。應用程式也可以透過 Datastore 查詢取得實體索引鍵,詳情請參閱「Datastore 查詢」頁面。

建立實體

在 Go 中,您可以透過下列方式建立新的實體:建立 Go 結構的執行個體、填入其欄位,並呼叫 datastore.Put 將實體儲存至 Datastore。只有匯出的欄位 (以大寫字母開頭) 會儲存至 Datastore。您可以將非空白的 stringID 引數傳送至 datastore.NewKey,藉此為實體指定索引鍵名稱:

employee := &Employee{
	FirstName: "Antonio",
	LastName:  "Salieri",
	HireDate:  time.Now(),
}
employee.AttendedHRTraining = true
key := datastore.NewKey(ctx, "Employee", "asalieri", 0, nil)
_, err = datastore.Put(ctx, key, employee)

如果您提供空白的金鑰名稱,或使用 datastore.NewIncompleteKey,Datastore 會自動為實體的金鑰產生數字 ID:

employee := &Employee{
	FirstName: "Antonio",
	LastName:  "Salieri",
	HireDate:  time.Now(),
}
employee.AttendedHRTraining = true
key := datastore.NewIncompleteKey(ctx, "Employee", nil)
_, err = datastore.Put(ctx, key, employee)

擷取實體

如要擷取以特定索引鍵識別的實體,請將 *datastore.Key 做為引數傳送至 datastore.Get 函式。您可以使用 datastore.NewKey 函式產生 *datastore.Key

employeeKey := datastore.NewKey(ctx, "Employee", "asalieri", 0, nil)
addressKey := datastore.NewKey(ctx, "Address", "", 1, employeeKey)
var addr Address
err = datastore.Get(ctx, addressKey, &addr)

datastore.Get 會填入適當 Go 結構的例項。

更新實體

如要更新現有實體,請修改結構的屬性,然後呼叫 datastore.Put。此資料會覆寫現有實體。每次呼叫 datastore.Put 時,系統會將整個物件傳送至 Datastore。

刪除實體

只要有實體的金鑰,就可以透過 datastore.Delete 函式刪除該實體:

key := datastore.NewKey(ctx, "Employee", "asalieri", 0, nil)
err = datastore.Delete(ctx, key)

批次作業

datastore.Putdatastore.Getdatastore.Delete 有名為 datastore.PutMultidatastore.GetMultidatastore.DeleteMulti 的大量變化版本。這些方法可在單一 Datastore 呼叫中對多個實體執行動作:

// A batch put.
_, err = datastore.PutMulti(ctx, []*datastore.Key{k1, k2, k3}, []interface{}{e1, e2, e3})

// A batch get.
var entities = make([]*T, 3)
err = datastore.GetMulti(ctx, []*datastore.Key{k1, k2, k3}, entities)

// A batch delete.
err = datastore.DeleteMulti(ctx, []*datastore.Key{k1, k2, k3})

批次作業不會影響費用。無論每個索引鍵存在與否,您都必須為批次作業中的每一個索引鍵付費。作業中實體的大小不會影響費用。