【中央数据库模式难扩展】绝大多数的Web应用在处理一个为了以后的请求作检索用的请求时,需要存储信息。<1.Most useful web applications need to store information during the handling of a request for retrieval during a later request.>一个小网站的典型安排就是包含一个用于整个网站的数据库服务器,然后一个或多个Web服务器连接到这个数据库进行数据的存储与获取。使用一个中央数据库服务器使得数据有一个正规表示(canonical representation)变得容易。所以多个用户访问多个Web服务器看到的是相同的最新的数据。但是一旦中央服务器达到了它的同时连接量时,就很难扩展。
【不同的应用需要不同的数据库系统】在这过去二十年里,用于Web应用的最流行的存储系统就是关系型数据库。用行列构成表实现空间上的高效简洁(space efficiency and concision)。使用原始计算能力(raw computing power)以及索引来执行查询。特别是Join查询可以将多个相关的记录作为一个查询单元。其他的数据存储系统包括层次数据存储(hierarchical datastores,如:文件系统,XML数据库)和对象数据库。每种数据库都有优缺点,哪一种是最适合于应用的,这取决于应用数据的类型和访问方式。每一种数据库有它自己的技术来跨越一个服务器。(grow past the first server)
【Google App Engine数据库系统易扩展】Google App Engine的数据库系统极其类似于一个对象数据库。它不是一个连接-查询(join-query)的关系型数据库。如果你来自使用关系型数据库的Web应用,你很有可能需要改变原有的思考应用数据的方式。使用运行时,App Engine datastore 是一个抽象。它允许App Engine来处理分发应用和扩展应用的细节。<2.As with the runtime environment, the design of the App Engine datastore is an abstraction that allows App Engine to handle the details of distributing and scaling the application.>因而你的代码可以关注于其他方面。
实体和属性(Entities and Properties)
App Engine应用把它的数据存储为一个或多个datastore实体(datastore entity)。一个实体有一个或多个属性,每个属性有一个名字和一个值,值是几种原始值类型的一种。每个实体属于一个种类(each entity is of a named kind)。种类将实体进行分类以便查询。
初看,这就像是一个关系型数据库:种类的实体就像表的行,属性就像列(字段)。然而,在实体和行之间有两个主要的不同点:一是,给定的种类的一个实体和相同种类的其他实体不一定有相同的属性。二是,两个实体的相同属性的值可以有不同的类型。在这方面,datastore实体是无模式的。正如将会看到的,这种设计提供了强大的灵活性的同时也面临着维护的挑战。
实体和表的行还有一个不同点是实体的属性可以有多个值。这个特性有一点诡异但是一旦理解了相当有用。
每一个实体都有一个唯一的键。由应用提供或App Engine生成。和关系型数据库不同,这个键不是一个字段或属性,而是实体的一个独立部分。使用key,你可以快速获取一个实体,可以用key值执行查询。
在实体创建之后它的key是不可以改变的。种类也不能改变。App Engine使用实体的种类和key来决定这个实体被存储在一个大型服务器集的什么位置。然而key和种类却不能确保两个实体保存在同一个服务器上。
查询和索引(Queries and indexes)
一个datastore查询返回零个或多个同一个种类的实体。查询可以返回实体的key。查询可以根据条件过滤出属性值满足的实体,也可以返回根据属性值排过序的实体。查询还可以使用key过滤排序。
在一个典型的关系型数据库中,查询是根据表结构进行设计和执行的。表又是由开发人员设计好存储的。开发人员可以让数据库产生和维护确定列上的索引来加快某些查询。
App Engine却不相同。使用App Engine,每一个查询都有一个由datastore维护的相关索引。当应用执行一个查询时,datastore为这个查询查找索引,扫描到第一个匹配行,然后返回索引中连续的实体,直到某行不再匹配。
当然,这要求App Engine提前知道应用将执行哪些查询。没有必要知道过滤器的值,但是需要知道查询的实体种类,过滤和排序的属性,过滤的操作,排序的顺序。
默认情况下,App Engine为简单查询提供了一个索引集合,这些索引基于实体上的属性。对于复杂的查询,应用必须在它的配置中包含索引说明。在你本地的开发用的Web服务器上测试你的应用的时候,App Engine SDKs通过查看哪些查询被执行了来帮助生成这个配置文件。当你上传应用的时候,datastore就知道为在测试期间被应用执行过的查询创建索引。你也可以手动地编辑这个索引配置。
当你的应用创建新的实体和更新存在的实体时,datastore会更新每一个相关的索引。以牺牲实体更新效率为代价(很有可能在做一个单一的变更时许多的表需要更新),使得查询相当快(每一个查询就是一个简单的表扫描)。实际上,基于索引的查询的性能是不受datastore中实体数量的影响的,而只受到结果集大小的影响。
索引是值得关注的,它会占用空间,更新实体所花的时间也会增长。我们会在第6章讨论索引的细节。
事务
当一个应用有多个客户端同时尝试读写相同的数据的时候,数据保证处于一致的状态是必须的。一个用户永远不会看到因另一个用户的动作还没有完成的才写了一半的数据或没有意义的数据。<3.One user should never see half-written data or data that doesn't make sense because another user's action hasn't completed.>
当应用对一个实体的属性进行更新的时候,App Engine确保对这个实体的更新全部成功,或全部失败并且实体处于更新之前的那个状态。其他的用户不会看到任何影响直到成功改变。
换句话讲,单个实体的变更是在一个事务中发生的。每一个事务都是原子性的:事务要么全部成功,要么全部失败。不会部分成功或失败。
在单个事务中应用会读取或更新多个实体,但是当它创建这些实体时必须告诉App Engine哪些实体会被一起更新。应用通过创建实体组来做到这点。App Engine使用实体组控制实体在多个服务器之间分发,所以可以保证在一个实体组中的事务全部成功或失败。在数据库方面,App Engine datastore天然地支持本地事务(local transactions)。<4.In database terms, the App Engine datastore natively supports local transactions.>
当一个应用调用datastore API来更新实体时,这个调用只在事务成功或失败后返回,并且返回成功或失败。对于更新而言,这意味着在返回结果前服务都等待着所有的实体被更新。应用可以异步地调用datastore,因而当datastore在准备结果时,应用代码可以继续执行。但是这个更新本身不会返回直到它确认了这个变更。
在一个用户正在更新实体时,如果另一个用户尝试去更新这个实体,datastore会立即返回一个竞争失败异常。想象一下,两个用户在竞争数据的一个部分,第一个做了更新提交的用户赢了。另一个用户必须再次尝试它的操作,可能从新数据中再读值并执行更新。竞争是希望的,所以重试也是正常的。在数据库方面,App Engine使用乐观的并发控制(optimistic concurrency control):每一个用户都是乐观的,它的提交将会成功,所以它不是通过在数据上加锁来做到这点的。
由于竞争,读实体从不失败。应用看到的是它最近的稳定的状态。你还可以从相同的实体组中读取多个实体,使用事务来保证组中的所有数据是新的并且一致的。
在大多数的情况下,在一个竞争的实体中重试一个事务将会成功。但是一个应用被设计成许多用户可以更新单个实体,那个应用变得越流行,用户越有可能竞争失败。通过设计实体组来避免高竞争失败率是重要的,尤其是大量的用户的时候。
在同一个事务中读写数据总是重要的。比如,应用可以开始一个事务,读取一个实体,根据最新得到的值更新一个属性值,保存实体,然后提交事务。在这种情况下,没有其他事务的冲突,整个事务成功的话,保存操作才会发生。如果有一个冲突,应用想再试的话,应用应当重试整个事务:再次读取整个实体(有可能已经更新了),使用新值来计算,再次尝试更新。通过在事务中包含读操作,datastore认为来自多个同时的请求的相关的读写不会交织生成不一致的结果。(?)
使用索引和乐观并发控制,App Engine datastore被设计来给需要快速读取数据,保证数据一致,随着用户数量和数据量自动扩展的应用使用。这些目标和关系型数据库的目标有一些不同,它们更适用于Web应用。