Python 2.7 已达到支持终止期限,并将于 2026 年 1 月 31 日
弃用。弃用后,您将无法部署 Python 2.7 应用,即使您的组织之前曾使用组织政策重新启用旧版运行时的部署也是如此。现有的 Python 2.7 应用在
弃用日期之后将继续运行并接收流量。我们建议您
迁移到最新支持的 Python 版本。
合理设计数据结构以实现高度一致性
使用集合让一切井井有条
根据您的偏好保存内容并对其进行分类。
注意:强烈建议构建新应用的开发者使用 NDB 客户端库,它与 DB 客户端库相比具有多项优势,例如可通过 Memcache API 进行自动实体缓存。如果您当前使用的是较早的 DB 客户端库,请参阅 DB 到 NDB 的迁移指南
Datastore 可将数据分布到多台机器,并在广泛的地理区域中使用同步复制,因而具备高可用性、扩缩性和耐用性。但是,此设计也有所折衷,即任何单个实体组的写入吞吐量均被限制为大约每秒提交一次,并且跨多个实体组进行查询或执行事务也受到限制。本页面详细介绍了这些限制,并讨论了设计数据结构的最佳做法,以便在支持高度一致性的同时仍满足应用的写入吞吐量要求。
具备高度一致性的读取始终返回最新数据,如果在事务中执行此类操作,则返回的数据看上去来自单个一致的快照。不过,为了确保高度一致性或参与事务,查询必须指定祖先实体过滤条件,并且事务最多可涉及 25 个实体组。具备最终一致性的读取没有这些限制,并且足以应对大多数情况。借助具备最终一致性的读取,您可以将数据分布到更多的实体组中,并对不同实体组并行执行提交操作,从而实现更高的写入吞吐量。不过,您需要了解具备最终一致性的读取的特性,以确定其是否适合您的应用:
- 这些读取操作的结果可能不会反映最新的事务。发生这种情况的原因是,这些读取操作并不能确保它们是针对最新副本运行。相反,它们会使用在执行查询时该副本上可用的任何数据。复制延迟时间通常不会超过几秒钟。
- 跨多个实体的已提交事务可能看上去已应用于这些实体中的一部分,但未应用于其他实体。但请注意,事务永远不会在单个实体中显示为部分应用。
- 查询结果可能包含不符合过滤条件的实体,还可能排除符合过滤条件的实体。发生这种情况是因为,从中读取索引的版本可能与从中读取实体本身的版本不同。
如需了解如何设计数据结构以确保强一致性,请对用于一个简单留言板应用的两种不同方法进行比较。第一种方法是为创建的每个实体创建一个新的根实体:
import webapp2
from google.appengine.ext import db
class Guestbook(webapp2.RequestHandler):
def post(self):
greeting = Greeting()
...
然后,该方法会对实体种类 Greeting
查询最新的十条问候。
import webapp2
from google.appengine.ext import db
class MainPage(webapp2.RequestHandler):
def get(self):
self.response.out.write('<html><body>')
greetings = db.GqlQuery("SELECT * "
"FROM Greeting "
"ORDER BY date DESC LIMIT 10")
不过,由于您使用的是非祖先查询,因此在该方案中,用于执行查询的副本在执行查询的时候可能尚未发现新的问候。尽管如此,几乎所有写入都可在提交几秒钟之后用于非祖先查询。对许多应用而言,在当前用户自己更改的情况下,提供非祖先查询结果的解决方案通常足以将此类复制延迟时间控制在完全可接受的范围。
如果强一致性对于您的应用非常重要,另一种方法是写入具有祖先实体路径的实体,该路径标识必须在具备强一致性的单个祖先查询中读取的所有实体的同一根实体:
import webapp2
from google.appengine.ext import db
class Guestbook(webapp2.RequestHandler):
def post(self):
guestbook_name=self.request.get('guestbook_name')
greeting = Greeting(parent=guestbook_key(guestbook_name))
...
然后,您将能够在由该公共根实体标识的实体组内执行具备高度一致性的祖先查询:
import webapp2
from google.appengine.ext import db
class MainPage(webapp2.RequestHandler):
def get(self):
self.response.out.write('<html><body>')
guestbook_name=self.request.get('guestbook_name')
greetings = db.GqlQuery("SELECT * "
"FROM Greeting "
"WHERE ANCESTOR IS :1 "
"ORDER BY date DESC LIMIT 10",
guestbook_key(guestbook_name))
此方法通过针对每个留言板向单个实体组写入数据来实现强一致性,但也将对留言板的更改限制为每秒不超过 1 次写入(支持的实体组限制)。如果应用有可能遇到更频繁的写入使用情况,您可能需要考虑使用其他方式:例如,您可以将最近发布的帖子放入设有过期时间的 Memcache 中,并混合显示来自 Memcache 和 Datastore 的最新帖子,或者可以利用 Cookie 缓存这些帖子、将一些状态添加到网址中或采用其他完全不同的方法。目标是找到一个缓存解决方案,在当前用户向应用发布消息的时间段内为该用户提供数据。请记住,如果在事务内执行 get、祖先查询或任何操作,您会始终看到最新写入的数据。
如未另行说明,那么本页面中的内容已根据知识共享署名 4.0 许可获得了许可,并且代码示例已根据 Apache 2.0 许可获得了许可。有关详情,请参阅 Google 开发者网站政策。Java 是 Oracle 和/或其关联公司的注册商标。
最后更新时间 (UTC):2025-09-04。
[[["易于理解","easyToUnderstand","thumb-up"],["解决了我的问题","solvedMyProblem","thumb-up"],["其他","otherUp","thumb-up"]],[["很难理解","hardToUnderstand","thumb-down"],["信息或示例代码不正确","incorrectInformationOrSampleCode","thumb-down"],["没有我需要的信息/示例","missingTheInformationSamplesINeed","thumb-down"],["翻译问题","translationIssue","thumb-down"],["其他","otherDown","thumb-down"]],["最后更新时间 (UTC):2025-09-04。"],[[["\u003cp\u003eDevelopers should utilize the NDB Client Library for new applications due to its advantages, such as automatic entity caching.\u003c/p\u003e\n"],["\u003cp\u003eDatastore offers high availability and scalability, but this comes with a limitation of about one commit per second for any single entity group.\u003c/p\u003e\n"],["\u003cp\u003eStrongly-consistent reads require an ancestor filter and provide current data within transactions, while eventually-consistent reads lack these constraints but might return outdated or incomplete data.\u003c/p\u003e\n"],["\u003cp\u003eFor applications requiring strong consistency, entities can be written with an ancestor path, but this method limits changes within a guestbook to one write per second.\u003c/p\u003e\n"],["\u003cp\u003eApplications encountering heavier write usage may consider caching strategies, such as using memcache or cookies, to manage recent posts and improve responsiveness for the current user.\u003c/p\u003e\n"]]],[],null,["# Structuring Data for Strong Consistency\n\n**Note:**\nDevelopers building new applications are **strongly encouraged** to use the\n[NDB Client Library](/appengine/docs/legacy/standard/python/ndb), which has several benefits\ncompared to this client library, such as automatic entity caching via the Memcache\nAPI. If you are currently using the older DB Client Library, read the\n[DB to NDB Migration Guide](/appengine/docs/legacy/standard/python/ndb/db_to_ndb)\n\nDatastore provides high availability, scalability and durability by\ndistributing data over many machines and using synchronous\nreplication over a wide geographic area. However, there is a tradeoff in this\ndesign, which is that the write throughput for any single\n[*entity group*](/appengine/docs/legacy/standard/python/datastore/entities#Ancestor_paths) is limited to about\none commit per second, and there are limitations on queries or transactions that\nspan multiple entity groups. This page describes these limitations in more\ndetail and discusses best practices for structuring your data to support strong\nconsistency while still meeting your application's write throughput\nrequirements.\n\nStrongly-consistent reads always return current data, and, if performed within a\ntransaction, will appear to come from a single, consistent snapshot. However,\nqueries must specify an ancestor filter in order to be strongly-consistent or\nparticipate in a transaction, and transactions can involve at most 25 entity\ngroups. Eventually-consistent reads do not have those limitations, and are\nadequate in many cases. Using eventually-consistent reads can allow you to\ndistribute your data among a larger number of entity groups, enabling you to\nobtain greater write throughput by executing commits in parallel on the\ndifferent entity groups. But, you need to understand the characteristics of\neventually-consistent reads in order to determine whether they are suitable for\nyour application:\n\n- The results from these reads might not reflect the latest transactions. This can occur because these reads do not ensure that the replica they are running on is up-to-date. Instead, they use whatever data is available on that replica at the time of query execution. Replication latency is almost always less than a few seconds.\n- A committed transaction that spanned multiple entities might appear to have been applied to some of the entities and not others. Note, though, that a transaction will never appear to have been partially applied within a single entity.\n- The query results can include entities that should not have been included according to the filter criteria, and might exclude entities that should have been included. This can occur because indexes might be read at a different version than the entity itself is read at.\n\nTo understand how to structure your data for strong consistency, compare two\ndifferent approaches for a simple guestbook application. The first approach\ncreates a new root entity for each entity that is created: \n\n import webapp2\n from google.appengine.ext import db\n\n class Guestbook(webapp2.RequestHandler):\n def post(self):\n greeting = Greeting()\n ...\n\nIt then queries on the entity kind `Greeting` for the ten most recent greetings. \n\n import webapp2\n from google.appengine.ext import db\n\n class MainPage(webapp2.RequestHandler):\n def get(self):\n self.response.out.write('\u003chtml\u003e\u003cbody\u003e')\n greetings = db.GqlQuery(\"SELECT * \"\n \"FROM Greeting \"\n \"ORDER BY date DESC LIMIT 10\")\n\nHowever, because you are using a non-ancestor query, the replica used to perform\nthe query in this scheme might not have seen the new greeting by the time the\nquery is executed. Nonetheless, nearly all writes will be available for\nnon-ancestor queries within a few seconds of commit. For many applications, a\nsolution that provides the results of a non-ancestor query in the context of the\ncurrent user's own changes will usually be sufficient to make such replication\nlatencies completely acceptable.\n\nIf strong consistency is important to your application, an alternate approach is\nto write entities with an ancestor path that identifies the same root entity\nacross all entities that must be read in a single, strongly-consistent ancestor\nquery: \n\n import webapp2\n from google.appengine.ext import db\n\n class Guestbook(webapp2.RequestHandler):\n def post(self):\n guestbook_name=self.request.get('guestbook_name')\n greeting = Greeting(parent=guestbook_key(guestbook_name))\n ...\n\nYou will then be able to perform a strongly-consistent ancestor query within the\nentity group identified by the common root entity: \n\n import webapp2\n from google.appengine.ext import db\n\n class MainPage(webapp2.RequestHandler):\n def get(self):\n self.response.out.write('\u003chtml\u003e\u003cbody\u003e')\n guestbook_name=self.request.get('guestbook_name')\n\n greetings = db.GqlQuery(\"SELECT * \"\n \"FROM Greeting \"\n \"WHERE ANCESTOR IS :1 \"\n \"ORDER BY date DESC LIMIT 10\",\n guestbook_key(guestbook_name))\n\nThis approach achieves strong consistency by writing to a single entity group\nper guestbook, but it also limits changes to the guestbook to no more than\n1 write per second (the supported limit for entity groups). If your application\nis likely to encounter heavier write usage, you might need to consider using\nother means: for example, you might put recent posts in a\n[memcache](/appengine/docs/legacy/standard/python/memcache) with an expiration\nand display a mix of recent posts from the memcache and\nDatastore, or you might cache them in a cookie, put some state\nin the URL, or something else entirely. The goal is to find a caching solution\nthat provides the data for the current user for the period of time in which the\nuser is posting to your application. Remember, if you do a get, an ancestor\nquery, or any operation within a transaction, you will always see the most\nrecently written data."]]