Google Code 提供下列語言介面: English - Español - 日本語 - 한국어 - Português - Pусский - 中文(简体) - 中文(繁體)
每個資料存放區查詢都會使用索引,而索引是一個表格,當中列有依需求排序的查詢結果。「應用服務引擎」應用程式會在名稱為 index.yaml 的設定檔中定義其索引。 如果開發網頁伺服器遇到尚未設定索引的查詢,就會自動將建議新增至這個檔案。您可以在上傳應用程式之前編輯該檔案,以手動調整索引。
索引式查詢機制支援大部分常見的查詢種類,但是可能不支援您在其他資料庫技術中使用的部分查詢。查詢的限制及其解釋如下所述。
查詢會從資料存放區抓取符合一組條件的實體。查詢會指定實體種類,根據實體屬性值的零或多個條件 (有時候稱為「篩選器」),以及零或多個排序順序描述。執行查詢時,它會擷取符合指定條件之指定種類的所有實體,以描述的順序排序。
Datastore Python API (資料存放區 Python API) 提供兩種介面,來準備和執行查詢:Query 介面,使用方法來準備查詢,以及 GqlQuery 介面,使用類似 SQL 查詢語言 (稱為 GQL),從查詢字串來準備查詢。在「建立、取得和刪除資料:使用查詢取得實體」以及相關參考資料中,對這些介面有更詳細的說明。
class Person(db.Model):
first_name = db.StringProperty()
last_name = db.StringProperty()
city = db.StringProperty()
birth_year = db.IntegerProperty()
height = db.IntegerProperty()
# The Query interface prepares a query using instance methods.
q = Person.all()
q.filter("last_name =", "Smith")
q.filter("height <", 72)
q.order("-height")
# The GqlQuery interface prepares a query using a GQL query string.
q = db.GqlQuery("SELECT * FROM Person " +
"WHERE last_name = :1 AND height < :2 " +
"ORDER BY height DESC",
"Smith", 72)
# The query is not executed until results are accessed.
results = q.fetch(5)
for p in results:
print "%s %s, %d inches tall" % (p.first_name, p.last_name, p.height)
篩選器包括屬性名稱、比較運算子及值。如果篩選器具有特定名稱的屬性,且其值可與運算子所描述的特定值比較,則實體會符合篩選器。如果實體符合所有的篩選器,則實體會是查詢的結果
篩選運算子可以是下列其中之一:
< 小於<= 小於或等於= 等於> 大於>= 大於或等於!= 不等於IN 等於所提供清單中的任何值。!= 運算子實際上會執行 2 個查詢:一個是其他篩選器保持原狀,但以小於篩選器取代不等於篩選器,另一個會以大於篩選器取代不等於篩選器。結果會按照順序合併。就像下方討論的不等式篩選器一樣,一個查詢只能有一個不等於篩選器,這種查詢不能有其他的不等式篩選器。
IN 運算子也會執行多個查詢,一個是針對提供之清單值的所有項目使用相同的篩選器,但以等於篩選器取代 IN 篩選器。結果會按照清單中的項目順序合併。如果查詢超過 IN 篩選器,則查詢會以多個查詢的方式來執行,一次對 IN 中的值組合執行一個查詢。
包含 != 和 IN 運算子的單一查詢限制是 30 個子查詢。
對於每個應用程式執行的查詢動作,「應用服務引擎」資料存放區都會編出索引。如果應用程式變更了資料存放區實體,資料存放區就會以正確的結果來更新索引。應用程式執行查詢時,資料存放區會直接從相應的索引擷取結果。
針對查詢中使用的種類、篩選器屬性和運算子、排序順序的組合,應用程式都有索引。請參考 GQL 當中的查詢範例:
SELECT * FROM Person WHERE last_name = "Smith"
AND height < 72
ORDER BY height DESC
此查詢的索引是 Person 種類之實體的金鑰表格,而欄中是 height 的值和 last_name 的屬性。索引會按照 height 以遞減方式排序。
相同格式,但不同篩選值的兩個查詢,會使用相同的索引。例如,下列查詢使用的索引,與上述的查詢相同:
SELECT * FROM Person WHERE last_name = "Jones"
AND height < 63
ORDER BY height DESC
資料存放區使用下列步驟來執行查詢:
索引表格包含篩選器或排序順序所使用之每個屬性的欄。列會按照下列各項依順序排序:
注意:根據索引的目的,IN 篩選器會像 = 篩選器一樣來處理,而 != 篩選器則會像其他不等式篩選器來處理。
這樣會將使用此索引之每個可能查詢的所有結果,放在表格中的連續列中。
提示:「查詢篩選器」沒有明確的方式可以比對字串值的某一部分,但是您可以使用不等式篩選器進行擬字首比對:db.GqlQuery("SELECT * FROM MyModel WHERE prop >= :1 AND prop < :2", "abc", u"abc" + u"\ufffd")
這會比對每個 MyModel 實體,並找出 prop 字串屬性的前置字元為 abc 的實體。Unicode 字串 u"\ufffd" 代表 Unicode 字元的最大可能值。在排序索引中的屬性值時,落入此範圍的值即是所有擁有指定前置字元的值。
此機制支援的查詢種類十分廣泛,因此也適合大部分應用程式。不過,此機制並不支援您在其他資料庫技術所慣用的部分查詢種類。
實體必須有索引所參考的全部屬性,才會包含在索引中。如果實體沒有索引所參考的屬性,則實體不會出現在索引中,也就不可能會出現在使用該索引的查詢結果。
請注意,「應用服務引擎」資料存放區會區別沒有屬性以及屬性值為空值的實體 (None)。如果您要查詢同一種類的所有實體,可以使用負責指派預設值 (例如 None) 至查詢篩選器所使用之屬性的資料模型。
長文字類型 (Text) 或大型二進位類型 (Blob) 屬性值的屬性不會納入索引,因此無法提供查詢。
不會索引這些屬性值的結果,就是在屬性使用篩選器或排序順序的查詢,不會比對屬性值為 Text 或 Blob 的實體。具有這類值的屬性,就像是設定與查詢篩選器和排序順序無關。
如果兩個實體的屬性擁有相同名稱,但是屬性值的類型不同,則屬性的索引會先依照屬性值類型排序實體,再依照每個類型適用的順序排序。舉例來說,如果兩個實體均擁有「年齡」屬性,但是其中一個的屬性值為整數,而另一個的屬性值為字串,則使用「年齡」屬性排序時,擁有整數值的實體會排在擁有字串值的實體前面,無論兩者屬性值為何。
當兩個實體的屬性值分別為整數和浮點數時,請特別留意,因為這兩種類型的差別較難以辨識,可是在資料存放區中確實為不同的類型。整數的順序比浮點高,因此擁有整數值 38 的屬性會排在擁有浮點值 37.5 的屬性前面。
「應用服務引擎」按照預設,會為每個簡單的查詢建置索引。針對其他查詢,應用程式必須在設定檔中指定它需要的索引,此設定檔的名稱為 index.yaml。 若「應用服務引擎」下的應用程式嘗試執行查詢,卻沒有相應的索引 (不論是預設所提供或 index.yaml 所描述的),則查詢將會失敗。
「應用服務引擎」會為下列格式的查詢提供自動索引:
其他型式的查詢都必須在 index.yaml 中指定其索引,包括:
開發網頁伺服器使得索引設定的管理更加輕鬆:需要索引的查詢如果沒有索引將無法執行,但是開發網頁伺服器可以產生索引的設定檔,成功地執行查詢。本機測試應用程式時,如果呼叫了應用程式可能執行的所有查詢 (各種種類、上階、篩選器和排序順序的組合),則產生的項目將代表一組完整的索引。如果測試並未執行每種可能的查詢格式,您可以在上傳應用程式之前,檢視並修改索引的設定檔。
「應用服務引擎」按照預設,會為每個簡單的查詢建置索引。針對其他查詢,應用程式必須在設定檔中指定它需要的索引,此設定檔的名稱為 index.yaml。 若「應用服務引擎」下的應用程式嘗試執行查詢,卻沒有相應的索引 (不論是預設所提供或 index.yaml 所描述的),則查詢將會失敗。
index.yaml 描述每個索引表格,包括其種類、查詢篩選器和排序順序所需的屬性,以及查詢是否使用上階子句 (可以是 Query.ancestor() 或 GQL ANCESTOR IS 子句)。 屬性會以它們的排序順序列出:首先是等式或 IN 篩選器使用的屬性,接著是不等式篩選器使用的屬性,然後是查詢結果排列順序及其方向。
再次看看下方的範例查詢:
SELECT * FROM Person WHERE last_name = "Smith"
AND height < 72
ORDER BY height DESC
若應用程式只執行此查詢 (可能還有類似的查詢,只是 "Smith" 和 72 的值不同),則 index.yaml 檔案看起來會像這樣:
indexes:
- kind: Person
properties:
- name: last_name
- name: height
direction: desc
實體建立或更新時,也會更新每個適當的索引。套用至實體的索引數量,會影響建立或更新實體的時間。
如需關於 index.yaml 語法的詳細資訊,請參閱設定索引。
實體金鑰可以是查詢篩選器或排序順序的主體,並在屬性名稱中使用特殊名稱 __key__。資料存放區會考慮這類查詢的完整金鑰值,包括實體的父系路徑、種類,以及應用程式指派的金鑰名稱字串或系統指派的數字 ID。
實體金鑰在系統的所有實體中是唯一的,因此 __key__ 查詢可以輕鬆地批次擷取指定種類的實體,例如批次傾出資料存放區的內容。不同於 offset,這個方法不會因為實體數目的多寡而影響效率。例如:
class MainHandler(webapp.RequestHandler):
def get(self):
query = Entity.gql('ORDER BY __key__')
# Use a query parameter to keep track of the last key of the last
# batch, to know where to start the next batch.
last_key_str = self.request.get('last')
if last_key_str:
last_key = db.Key(last_key_str)
query = Entity.gql('WHERE __key__ > :1 ORDER BY __key__', last_key)
# For batches of 20, fetch 21, then use result #20 as the "last"
# if there is a 21st.
entities = query.fetch(21)
new_last_key_str = None
if len(entities) == 21:
new_last_key_str = str(entities[19].key())
# Return the data and new_last_key_str. Client would use
# http://...?last=new_last_key_str to fetch the next batch.
# ...
金鑰會先依照父系路徑排序,接著依照種類,再依照金鑰名稱或 ID 排序。種類和金鑰名稱為字串,並使用位元組值排序。ID 為整數,並使用數字大小排序。如果實體的父系和種類相同,卻混合使用字串名稱和數字 ID 的金鑰,則擁有數字 ID 的實體會排在擁有字串名稱的實體後面。父系路徑元素的比較方式也很類似:先依照種類 (字串) 排序,再依照金鑰名稱 (字串) 或 ID (數字) 排序。
使用具有索引之金鑰的查詢,跟使用屬性的查詢十分相像,唯一的差異在於:使用等式篩選器篩選金鑰、且擁有其他篩選器的查詢,必須使用應用程式的索引設定檔所定義的自訂索引。與所有的查詢一樣,當需要自訂索引的查詢遭到測試時,開發網頁伺服器會在這個檔案中建立適當的設定。
索引查詢機制本身具有一些執行查詢的限制。
屬性的查詢篩選器條件或排序順序,隱含一個條件,那就是實體的該屬性要有值。
如果其他實體具有相同種類的屬性,資料存放區實體的該屬性便不需要有值。屬性的篩選器只能比對該屬性有值的實體。在篩選器或排序順序中使用的實體屬性若沒有值,為查詢建置的索引會省略這樣的實體。
實體如果缺乏指定屬性,便不可能進行查詢。您可以使用 None 預設值建立固定的 (模型化的) 屬性,再為屬性值為 None 的實體建立篩選器。
在查詢的所有篩選器中,只能在一個屬性上使用不等式篩選器 (<、<=、>=、>、!=)。
例如,這個查詢是成立的:
SELECT * FROM Person WHERE birth_year >= :min
AND birth_year <= :max
但是,這個查詢卻「不成立」,因為它在兩個不同的屬性上使用不等式篩選器:
SELECT * FROM Person WHERE birth_year >= :min_year
AND height >= :min_height # ERROR
在同一個查詢中,篩選器可以為不同屬性合併等式 (=) 比較,包括使用一或多個不等式條件的屬性。這是允許的:
SELECT * FROM Person WHERE last_name = :last_name
AND city = :city
AND birth_year >= :min_year
查詢的所有結果在索引表格中必須相鄰,否則查詢機制就必須掃描整個表格才能獲得結果。如果希望所有結果在表格中都維持連續,單一索引表格就不能代表多個屬性上的多個不等式篩選器。
如果某查詢同時具有含不等式比較的篩選器以及一或多個排序順序,則查詢必須包含該不等式的屬性排序順序,而該排序順序必須出現在其他屬性的排序順序之前。
這個查詢是「無效」的,因為它使用不等式篩選器,卻沒有依照篩選的屬性排序:
SELECT * FROM Person WHERE birth_year >= :min_year
ORDER BY last_name # ERROR
同樣地,這個查詢也是無效的,因為在使用其他屬性排序之前,它沒有先依照篩選的屬性排序:
SELECT * FROM Person WHERE birth_year >= :min_year
ORDER BY last_name, birth_year # ERROR
這個查詢是有效的:
SELECT * FROM Person WHERE birth_year >= :min_year
ORDER BY birth_year, last_name
為取得符合不等式篩選器的所有結果,查詢會掃描索引表格,找到第一個符合的列,再傳回所有連續的結果,直到找到不符合的列為止。如要使用連續列做為完整的結果集,這些列必須在其他排序順序之前,先依照不等式篩選器排序。
因為多重值屬性的索引方式不同,這些屬性的排序順序也十分特殊:
此排序順序會有不尋常的結果,在遞增和遞減排序中,[1, 9] 都會排在 [4, 5, 6, 7] 的前面。
請注意,如果查詢同時擁有等式篩選器和多重值屬性之排序順序,則排序順序會被忽略。對單一值屬性而言,這是一種簡單的最佳化方式。每個結果都會有相同的值,因此結果不需要進一步排序。
不過,多重值屬性可能擁有其他的值。因為排序順序被忽略,所以查詢結果傳回的順序可能會與套用排序順序的結果不同。(還原丟棄的排序順序的代價很高,而且需要額外的索引,不過這種情況很少發生,因此查詢規劃人員決定不處理。)
如上所述,每個實體的每個屬性 (不含 Text 或 Blob 值者) 都會被新增到至少一個索引表格,包括預設提供的簡單索引,以及應用程式參考屬性的 index.yaml 檔案所描述的任何索引。對於每個屬性都有單一個值的實體,「應用服務引擎」的簡單索引會儲存其屬性值一次,而每當自訂索引參考該屬性時,會再儲存一次。每次屬性值變更時,所有索引項目都必須更新,因此如果參考同一個屬性的索引增加,則該屬性的更新時間也會拉長。
為了防止實體的更新時間太長,資料存放區會限制單一實體可以擁有的索引項目數量。此限制訂得很寬鬆,因此大部分應用程式都不會有這個問題。然而,仍有部分情況可能會達到上限。例如實體具有非常多的單一值屬性,就可能超出索引項目限制。
擁有多個值的屬性會將每個值以個別項目儲存在索引中。因此,即使實體只有一個屬性,只要該屬性擁有多個值,仍可能會超過索引項目限制。
自訂索引如果參考多個多重值屬性,則只要幾個值就可能變得非常龐大。如要完整記錄這類屬性,索引表格必須為每個屬性的「每個屬性值排列」提供一列資料列。
例如,下列索引 (以 index.yaml 語法描述) 包括 x 和 y 屬性供種類 MyModel 的實體使用:
indexes: - kind: MyModel properties: - name: x - name: y
下列程式碼建立的實體,具有 x 屬性的兩個值以及 y 屬性的兩個值:
class MyModel(db.Expando): pass e2 = MyModel() e2.x = ['red', 'blue'] e2.y = [1, 2] e2.put()
如要正確呈現這些值,索引必須儲存 12 個屬性值:x 和 y 的內建索引分別使用 2 個,以及自訂索引中的 x 和 y 共 4 個排列分別使用 2 個。由於多重值屬性的值很多,因此索引可能需要為單一的實體儲存大量索引項目。您可以把參考多個多重值屬性的索引稱為「過量索引」,因為只要幾個屬性值,該索引就可能變得非常龐大。
如果 put() 導致索引項目的數量超過上限,則呼叫會失敗,並發生例外狀況。在您建立的新索引中,如果有部分索引項目都超出建立實體時的上限,就無法查詢該索引;而在「管理控制台」中,索引會顯示為「錯誤」狀態。
若要處理「錯誤」的索引,請先將它們從 index.yaml 檔案中移除,並執行 appcfg.py vacuum_indexes。然後,重新規劃索引定義和相關查詢,或者移除導致索引「過量」的實體。最後,將索引新增回 index.yaml,並執行 appcfg.py update_indexes。
只要避免查詢需要使用清單屬性的自訂索引,就可以避免過量索引的發生。如上所述,這包括使用多個排序順序的查詢、混合使用等式和不等式篩選器的查詢,以及使用上階篩選器的查詢。