實體、屬性和金鑰

附註:我們強烈建議建構新應用程式的開發人員使用 NDB 用戶端程式庫,因為 NDB 用戶端程式庫與本用戶端程式庫相較之下有幾個優點,例如能透過 Memcache API 自動將實體加入快取。如果您目前使用的是舊版的 DB 用戶端程式庫,請參閱從 DB 至 NDB 的遷移指南

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

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

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

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

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

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

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

Python App Engine SDK 包含資料模型程式庫,可將 Datastore 實體表示為 Python 類別的例項,並在 Datastore 中儲存及擷取這些例項。

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

種類及 ID

每個 Datastore 實體都屬於特定「種類」,可將實體分門別類,方便查詢。舉例來說,人力資源應用程式可以使用屬於 Employee 種類的實體來代表公司的每位員工。在 Python Datastore API 中,實體的種類是由「模型類別」決定,模型類別則是由您在應用程式中,以資料模型程式庫類別 db.Model 定義的子類別決定。模型類別的名稱即成為屬於該類別的實體的種類。以兩條底線 (__) 開頭的種類名稱均為保留名稱,不得使用。

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

import datetime
from google.appengine.ext import db


class Employee(db.Model):
  first_name = db.StringProperty()
  last_name = db.StringProperty()
  hire_date = db.DateProperty()
  attended_hr_training = db.BooleanProperty()


employee = Employee(first_name='Antonio',
                    last_name='Salieri')

employee.hire_date = datetime.datetime.now().date()
employee.attended_hr_training = True

employee.put()

Employee 類別會為資料模型宣告四個屬性:first_namelast_namehire_dateattended_hr_trainingModel 父類別會確保 Employee 物件的屬性與這個模型相符:舉例來說,如果您嘗試為 hire_date 屬性指派字串值,系統會發生執行階段錯誤,因為 hire_date 的資料模型已宣告為 db.DateProperty

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

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

如要指派索引鍵名稱給實體,請在建立實體時,將命名引數 key_name 提供給模型類別建構函式:

# Create an entity with the key Employee:'asalieri'.
employee = Employee(key_name='asalieri')

如要讓 Datastore 自動指派數字 ID,請省略 key_name 引數:

# Create an entity with a key such as Employee:8261.
employee = Employee()

指派 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 引數。這個引數的值可以是父系實體本身,也可以是父系實體的索引鍵。如要取得索引鍵,請呼叫父系實體的 key() 方法。以下範例會建立 Address 類型的實體,並示範兩種指定 Employee 實體做為父項的方式:

# Create Employee entity
employee = Employee()
employee.put()

# Set Employee as Address entity's parent directly...
address = Address(parent=employee)

# ...or using its key
e_key = employee.key()
address = Address(parent=e_key)

# Save Address entity to datastore
address.put()

交易和實體群組

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

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

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

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

屬性和值類型

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

支援的值類型如下:

值的類型 Python 類型 排序順序 附註
整數 int
long
數字 64 位元整數,帶正負號
浮點數 float 數字 64 位元雙精度,
IEEE 754
布林值 bool False<True
文字字串 (短) str
unicode
Unicode
(str 視為 ASCII)
最多 1500 個位元組
文字字串 (長) db.Text 最多 1 MB

未建立索引
位元組字串 (短) db.ByteString 位元組順序 最多 1500 個位元。
位元組字串 (長) db.Blob 最多 1 MB

未建立索引
日期與時間 datetime.date
datetime.time
datetime.datetime
依時間順序
地理點 db.GeoPt 依照緯度、
然後經度
郵遞地址 db.PostalAddress Unicode
電話號碼 db.PhoneNumber Unicode
電子郵件地址 db.Email Unicode
Google 帳戶使用者 users.User 電子郵件地址
(依 Unicode 順序)
即時通訊控點 db.IM Unicode
連結 db.Link Unicode
類別 db.Category Unicode
評分 db.Rating 數字
Datastore 索引鍵 db.Key 依路徑元素
(種類、ID、
種類、ID...)
Blobstore 金鑰 blobstore.BlobKey 位元組順序
零值 NoneType

重要事項:強烈建議您不要儲存 UserProperty,因為這個項目含有電子郵件地址和使用者的唯一識別碼。如果使用者變更電子郵件地址,但您還是使用先前儲存的 User 來比對新的 User 值,兩者將無法配對。

針對文字字串和未編碼的二進位資料 (位元組字串),Datastore 支援兩種值類型:

  • 短字串 (不超過 1,500 個位元組) 會建立索引,可用於查詢篩選器條件和排序順序。
  • 長字串 (不超過 1 MB) 不會建立索引,也不能用於查詢篩選器和排序順序。
注意:長位元組字串類型在 Datastore API 中的名稱為 Blob。這類型與 Blobstore API 中使用的 blob 無關。

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

  1. 空值
  2. 固定點數
    • 整數
    • 日期和時間
    • 評分
  3. 布林值
  4. 位元組序列
    • 位元組字串
    • Unicode 字串
    • Blobstore 索引鍵
  5. 浮點數
  6. 地理點
  7. Google 帳戶使用者
  8. Datastore 索引鍵

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

處理實體

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

建立實體

在 Python 中,您可以透過以下方式建立新的實體:建構特定模型類別的執行個體,視需要填入屬性,然後呼叫 put() 方法,將實體儲存至 Datastore。可傳送 key_name 引數至建構函式,藉此指定實體金鑰:

employee = Employee(key_name='asalieri',
                    first_name='Antonio',
                    last_name='Salieri')

employee.hire_date = datetime.datetime.now().date()
employee.attended_hr_training = True

employee.put()

如果您沒有提供索引鍵名稱,Datastore 會自動為實體的索引鍵產生數字 ID:

employee = Employee(first_name='Antonio',
                    last_name='Salieri')

employee.hire_date = datetime.datetime.now().date()
employee.attended_hr_training = True

employee.put()

擷取實體

如要擷取以特定索引鍵識別的實體,請將 Key 物件當做引數傳送至 db.get() 函式。您可以使用類別方法 Key.from_path() 產生 Key 物件。完整路徑是祖系路徑中的實體序列,每個實體都由其類型 (字串) 和 ID (金鑰名稱或數字 ID) 所代表:

address_k = db.Key.from_path('Employee', 'asalieri', 'Address', 1)
address = db.get(address_k)

db.get() 會傳回適當模型類別的例項。請務必確認已經匯入模型類別,以便擷取實體。

更新實體

如要更新現有實體,請修改物件的屬性,然後呼叫物件的 put() 方法。物件資料會覆寫現有實體,每次呼叫 put() 時,系統會將整個物件傳送至 Datastore。

如果要刪除屬性,請從 Python 物件中刪除該屬性:

del address.postal_code

然後儲存該物件。

刪除實體

如有實體的索引鍵,您就可以透過 db.delete() 函式刪除該實體

address_k = db.Key.from_path('Employee', 'asalieri', 'Address', 1)
db.delete(address_k)

或是呼叫實體本身的 delete() 方法:

employee_k = db.Key.from_path('Employee', 'asalieri')
employee = db.get(employee_k)

# ...

employee.delete()

批次作業

db.put()db.get()db.delete() 函式 (以及其非同步對應項目 db.put_async()db.get_async()db.delete_async()) 可接受清單引數,在單一 Datastore 呼叫中一次對多個實體執行動作:

# A batch put.
db.put([e1, e2, e3])

# A batch get.
entities = db.get([k1, k2, k3])

# A batch delete.
db.delete([k1, k2, k3])

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

刪除大量實體

如果需要刪除大量的實體,建議您使用 Dataflow 大量刪除實體

使用空白清單

針對 NDB 介面,資料儲存庫先前會將空白清單做為靜態和動態屬性的省略屬性寫入。為了維持向下相容的能力,會繼續以此做為預設行為。如想在全域範圍或各個 ListProperty 範圍內覆寫這項設定,請將 Property 類別中的 write_empty_list 引數設為 true,接著系統就會將空白清單寫入 Datastore,並以空白清單的形式讀取。

對於 DB 介面,在過去如果屬性為動態,則完全不容許寫入空白清單:如果嘗試此做法,就會出現錯誤狀況。這表示無須為了 DB 動態屬性,而保留向下相容的預設行為,如此一來,無須進行變更,即可在動態模型中讀取及寫入空白清單。

不過,對於 DB 靜態資源,空白清單會寫入為省略的資源,且為了回溯相容性,這項行為會繼續保留。如要為 DB 靜態屬性開啟空白清單,請在 Property 類別中將 write_empty_list 引數設為 true,接著系統就會將空白清單寫入 Datastore。