数据存储区查询将返回零个或多个单一类别的实体。它也可以只是返回查询所得的实体的键。查询不仅可以根据条件(必须匹配实体属性的值)进行过滤,还可以根据属性值对返回的实体进行排序。查询也可以通过键来进行筛选和排序。
在传统的关系型数据库中,查询会实时地根据数据表(由开发人员决定如何对其进行存储)进行计划和执行。开发人员还可以让数据库在指定的列上创建和维护索引以提高相关查询的执行速度。
App Engine的做法与此大不相同。在App Engine中,每个查询都有一个与之对应的由数据存储区维护的索引。当应用程序执行查询时,数据存储区将会找出该查询的索引,扫描并找到第一个与之匹配的行,然后逐行返回索引中的实体,直到发现与查询不匹配的行为止。
当然了,这需要App Engine预先掌握应用程序将要执行哪个查询才行。虽然它无须预先知道具体的筛选条件,不过却需要知道查询中实体的类别、用于筛选或排序的属性、筛选器的操作符以及排列顺序等。
默认情况下,App Engine会根据各类实体的既有属性为简单查询提供一组索引。为了执行更复杂的查询,应用程序必须在其配置文件中添加索引声明。当你在本地计算机上利用App Engine SDK所提供的那个开发版Web服务器测试应用程序时,App Engine SDK会监视所执行的查询并帮你生成一份配置文件。在你上传应用程序的时候,数据存储区就会自动地为测试时执行过的每条查询生成索引,你也可以手工编辑索引配置文件。
当应用程序创建了新实体或是更新了现有实体时,数据存储区会更新相应的索引。这也就使得查询变得非常快(每个查询都是一个简单的表扫描译注1),而实体的更新操作则反之(一个小小的修改就可能会使很多表译注2都需要更新)。事实上,基于索引的查询性能并不受数据存储区中实体数量的影响,而只会受到结果集大小的影响。
索引是值得关注的,因为它们会占用空间,而且会增加实体更新操作所需的时间。我们将在第5章中详细讨论索引。
译注1: 其实这里用“索引扫描”来比喻会更加贴切(我不知道GAE中索引的具体组织形式,虽然本书有所提及,但我始终认为G公司应该不会把索引做得如此简单,毕竟系统IO的速度在那摆着)。
译注2: 这里指定的应该是“索引”或“索引表”。同上,索引和表这两个概念也不一样。 当某个应用程序拥有大量需要同时读写相同数据的客户端时,保持数据的一致性状态就显得极为重要了。用户永远都不该看到由于其他用户的操作还没完成而产生的没写完的或是没有意义的数据。
当应用程序更新了某个实体之后,App Engine会确保该实体上的所有更新要么全部执行成功,要么全部失败且该实体恢复到更新执行之前的状态。在整个修改操作成功完成之前,其他用户将看不到该操作的效果。
换句话说,单个实体上的一次更新操作是在一个事务(transaction)中发生的。每个事务都是原子性的(atomic):事务要么全部成功,要么全部失败,而不能以更小的单位成功或失败。
应用程序可以在单个事务中读取或更新多个实体,不过它必须在创建这些实体的时候就告诉App Engine哪些实体将会一起更新。应用程序是通过在实体组(entity group)中创建实体来实现这一功能的。App Engine使用实体组来控制实体在服务器间的分布方式,这样做可以保证组上的事务全部成功或失败。用数据库的术语来说,App Engine数据存储区原生支持本地事务(local transaction)。
应用程序调用数据存储区API去更新实体时,在事务成功或失败之前,控制权是不会交回给应用程序的。另外,调用会返回成功或失败的信息。对于更新操作而言,这就意味着应用程序在做其他事情之前只能等待所有实体和索引被完整更新。
当其他用户的实体更新操作正在处理的时候,如果某个用户也试图去更新那个实体的话,数据存储区将会立即返回一个并发失败异常。应用程序通常应该在提示出错之前重试几次失败的事务,在事务中计算新值并在更新之前预先读取那些可能已经被修改过的数据。又用数据库的术语来说,App Engine数据存储区使用的是乐观并发控制(optimistic concurrency control)。
读取实体绝不会因为并发而失败,应用程序只会看到实体的最新稳定状态。你也可以在事务中执行多次读取操作,以确保在该事务中所读取的全部数据都是最新的和一致的。
大多数情况下,重试某个受争夺实体上的事务都是会成功的。但是,如果某个应用程序被设计为允许大量用户更新单个实体,那么这个应用程序越受欢迎,其用户就越有可能得到并发失败的结果。所以设计实体组来避免并发失败就显得很重要了,尤其是在用户数量很大的情况下。
应用程序可以将多个数据存储区操作放到一个事务中。比如说,应用程序可以启动一个事务,读取一个实体,根据最后一次读取的值更新一个属性,保存该实体,然后提交该事务。在这个例子中,只有当整个事务成功时(即没有与别的事务发生冲突),保存操作才会被执行。如果出现了一个冲突,且应用程序想要再试一次,那么就应该重试整个事务:再次读取那个实体(说不定已经被更新过了),使用新的值进行计算,然后再尝试一次更新。
由索引和乐观并发控制我们可以看出,App Engine的数据存储区是专为那些需要快速读取数据的应用程序而设计的,它将确保应用程序所看到的数据是一致的,而且能够根据用户数量以及数据大小自动伸缩。这些特点跟关系型数据库有点不同,它们尤其适用于Web应用程序。
《GAE 数据存储查询,索引和事务》留言数:0