到达里程碑 A 后,如果发出按键查找已更新实体的请求,一定会发现该实体的最新版本。不过,如果并发请求所执行的查询的谓词(对于你们这些 SQL/GQL 爱好者来说就是 WHERE 子句)不是由更新前的实体满足的,而是由更新后的实体满足的,则只有在应用操作到达里程碑 B 后执行查询时,实体才会出现在结果集中。
换句话说,在短暂的时段内,结果集可能不包含属性(根据按键查找的结果)满足查询谓词的实体。结果集中也有可能包含其属性不符合查询谓词要求的实体(同样根据按键查找的结果)。在确定要返回的实体时,查询不能考虑里程碑 A 和里程碑 B 之间的事务。系统将针对过时的数据执行查询,但对返回的键执行 get() 操作将始终获取该实体的最新版本。这意味着,在获取相应的实体后,您可能缺少与查询匹配的结果或者得到不匹配的结果。
[[["易于理解","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-07-30。"],[[["\u003cp\u003eCloud Datastore transactions operate at the Serializable isolation level, ensuring each transaction is completely isolated from others and that transactions within an entity group are executed sequentially.\u003c/p\u003e\n"],["\u003cp\u003eOutside of transactions, Datastore operations resemble the Read Committed isolation level, meaning retrieved entities will only reflect committed data and never show partially committed information.\u003c/p\u003e\n"],["\u003cp\u003eThe commit process in Cloud Datastore involves two milestones: the application of changes to the entity (Milestone A) and the application of changes to indices (Milestone B), and while commits are typically applied within milliseconds, reads may not immediately reflect changes until both milestones are reached.\u003c/p\u003e\n"],["\u003cp\u003eQueries that span multiple entity groups might return stale or partially applied results because they cannot determine if there are outstanding modifications before execution, whereas ancestor queries will always return current and consistent data.\u003c/p\u003e\n"],["\u003cp\u003eDepending on the timing of query execution relative to the commit milestones, result sets may exclude entities that satisfy the query predicate or include entities that no longer satisfy it, however, using a \u003ccode\u003eget()\u003c/code\u003e operation will always get the most up to date data.\u003c/p\u003e\n"]]],[],null,["# Transaction Isolation in App Engine\n\n*Max Ross* \nAccording to [Wikipedia](https://wikipedia.org/w/index.php?title=Isolation_%28database_systems%29&oldid=208946883),\nthe isolation level of a database management system \"defines how/when the\nchanges made by one operation become visible to other concurrent operations.\"\nThe goal of this article is to explain\n[query](/appengine/docs/python/datastore/queries) and\n[transaction](/appengine/docs/python/datastore/transactions)\nisolation in the Cloud Datastore used by App Engine. After reading this article\nyou should have a better understanding of how concurrent reads and writes\nbehave, both in and out of transactions.\n\nInside Transactions: Serializable\n---------------------------------\n\nIn order from strongest to weakest, the four isolation levels are\nSerializable, Repeatable Read, Read Committed, and Read Uncommitted. Datastore\ntransactions satisfy the Serializable isolation level. Each transaction is\ncompletely isolated from all other datastore transactions and operations.\nTransactions on a given entity group are executed serially, one after\nanother.\n\nSee the\n[Isolation\nand Consistency](/appengine/docs/python/datastore/transactions#Python_Isolation_and_consistency)\nsection of the transaction documentation for more information, as well as the Wikipedia article on\n[snapshot\nisolation](https://wikipedia.org/wiki/Snapshot_isolation).\n\nOutside Transactions: Read Committed\n------------------------------------\n\nDatastore operations *outside* transactions most closely resemble the\nRead Committed isolation level. Entities retrieved from the datastore by\nqueries or gets will only see committed data. A\nretrieved entity will never have partially committed data (some from before\na commit and some from after). The interaction between queries and transactions\nis a bit more subtle, though, and in order to understand it we need to look at\nthe commit process in more depth.\n\nThe Commit Process\n------------------\n\nWhen a commit returns successfully, the transaction is\nguaranteed to be applied, but that does not mean the result of your write is\nimmediately visible to readers. Applying a transaction consists of two milestones:\n\n- **Milestone A** -- the point at which changes to an entity have been applied\n- **Milestone B** -- the point at which changes to indices for that entity have been applied\n\n\u003cbr /\u003e\n\nIn Cloud Datastore, the transaction typically is completely applied within a few hundred milliseconds after the commit returns. However, even if it is not completely applied, subsequent reads, writes, and [ancestor queries](/appengine/docs/python/datastore/queries#Python_Ancestor_queries) will always reflect the results of the commit, because these operations apply any outstanding modifications before executing. However, queries that span multiple entity groups cannot determine whether there are any outstanding modifications before executing and may return stale or partially applied results.\n\nA request that looks up an updated entity by its key at a time after milestone A is guaranteed to see the latest version of that entity. However, if a concurrent request executes a query whose predicate (the `WHERE` clause, for you SQL/GQL fans out there) is not satisfied by the pre-update entity but is satisfied by the post-update entity, the entity will be part of the result set only if the query executes after the apply operation has reached milestone B.\n\nIn other words, during brief windows, it is possible for a result set not to include an entity whose properties, according to the result of a lookup by key, satisfy the query predicate. It is also possible for a result set to include an entity whose properties, again according to the result of a lookup by key, fail to satisfy the query predicate. A query cannot take into account transactions that are in between milestone A and milestone B when deciding which entities to return. It will be performed against stale data, but doing a `get()` operation on the returned keys will always get the latest version of that entity. This means that you could be either missing results that match your query or get results that do not match once you get the corresponding entity.\n\nThere are scenarios in which any pending modifications are guaranteed to be completely applied before the query executes, such as *any* ancestor queries in Cloud Datastore. In this case, query results will always be current and consistent.\n\nExamples\n--------\n\nWe've provided a general explanation of how concurrent updates and queries\ninteract, but if you're like me, you typically find it easier to get your\nhead around these concepts by working through concrete examples. Let's\nwalk through a few. We'll start with some simple examples and then\nfinish up with the more interesting ones.\n\nLet's say we have an application that stores Person entities. A Person\nhas the following properties:\n\n- Name\n- Height\n\nThis application supports the following operations:\n\n- `updatePerson()`\n- `getTallPeople()`, which returns all people over 72 inches tall.\n\nWe have 2 Person entities in the datastore:\n\n- Adam, who is 68 inches tall.\n- Bob, who is 73 inches tall.\n\nExample 1 - Making Adam Taller\n------------------------------\n\nSuppose an application receives two requests at essentially the same time.\nThe first request updates the height of Adam from 68 inches to 74 inches.\nA growth spurt! The second request calls getTallPeople().\nWhat does getTallPeople() return?\n\nThe answer depends on the relationship between the two commit milestones\ntriggered by Request 1 and the getTallPeople() query executed by Request 2.\nSuppose it looks like this:\n\n- Request 1, `put()`\n- Request 2, `getTallPeople()`\n- Request 1, `put()`--\\\u003e`commit()`\n- Request 1, `put()`--\\\u003e`commit()`--\\\u003emilestone A\n- Request 1, `put()`--\\\u003e`commit()`--\\\u003emilestone B\n\nIn this scenario, `getTallPeople()` will only return Bob. Why? Because the\nupdate to Adam that increases his height has not yet been committed, so\nthe change is not yet visible to the query we issue in Request 2.\n\nNow suppose it looks like this:\n\n- Request 1, `put()`\n- Request 1, `put()`--\\\u003e`commit()`\n- Request 1, `put()`--\\\u003e`commit()`--\\\u003emilestone A\n- Request 2, `getTallPeople()`\n- Request 1, `put()`--\\\u003e`commit()`--\\\u003emilestone B\n\nIn this scenario, the query executes before Request 1 reaches milestone B,\nso the updates to the Person indices have not yet been applied. As a result,\ngetTallPeople() only returns Bob. This is an example of a result set that\nexcludes an entity whose properties satisfy the query predicate.\n\nExample 2 - Making Bob Shorter (Sorry, Bob)\n-------------------------------------------\n\nIn this example we'll have Request 1 do something different. Instead of\nincreasing Adam's height from 68 inches to 74 inches, it will reduce Bob's\nheight from 73 inches to 65 inches. Once again, what does\n`getTallPeople()`\nreturn?\n\n- Request 1, `put()`\n- Request 2, `getTallPeople()`\n- Request 1, `put()`--\\\u003e`commit()`\n- Request 1, `put()`--\\\u003e`commit()`--\\\u003emilestone A\n- Request 1, `put()`--\\\u003e`commit()`--\\\u003emilestone B\n\nIn this scenario, `getTallPeople()` will return only Bob. Why? Because\nthe update to Bob that decreases his height has not yet been committed,\nso the change is not yet visible to the query we issue in Request 2.\n\nNow suppose it looks like this:\n\n- Request 1, `put()`\n- Request 1, `put()`--\\\u003e`commit()`\n- Request 1, `put()`--\\\u003e`commit()`--\\\u003emilestone A\n- Request 1, `put()`--\\\u003e`commit()`--\\\u003emilestone B\n- Request 2, `getTallPeople()`\n\n\nIn this scenario, `getTallPeople()` will return no one. Why? Because the\nupdate to Bob that decreases his height has been committed by the time\nwe issue our query in Request 2.\n\nNow suppose it looks like this:\n\n- Request 1, `put()`\n- Request 1, `put()`--\\\u003e`commit()`\n- Request 1, `put()`--\\\u003e`commit()`--\\\u003emilestone A\n- Request 2, `getTallPeople()`\n- Request 1, `put()`--\\\u003e`commit()`--\\\u003emilestone B\n\nIn this scenario, the query executes before milestone B, so the updates\nto the Person indices have not yet been applied. As a result,\n`getTallPeople()` still returns Bob, but the height property of the Person\nentity that comes back is the updated value: 65. This is an example of\na result set that includes an entity whose properties fail to satisfy\nthe query predicate.\n\nConclusion\n----------\n\nAs you can see from the above examples, the transaction isolation level\nof the Cloud Datastore is pretty close to Read Committed.\nThere are, of course, meaningful differences, but now that you understand\nthese differences and the reasons behind them, you should be in a better\nposition to make intelligent, datastore-related design decisions in your\napplications."]]