更新一下对GAE数据库的了解

标签:Google App Engine

从接触Google App Engine至今已有10个月了,随着版本的更新,datastore也在不断改善,支持了不少新的特性,所以我重新学习了一遍,并补上一些以前遗漏的知识。
当然,基于的是Python环境,毕竟使用和测试都较Java而言更为方便。

  • Data Viewer里可以看到有些实体的属性为<missing>和<null>。
    当创建实体时,模型的定义(描述)中不包含该属性时,该实体就不存在该属性,反应到数据库里就是<missing>;而如果定义了该属性,却未对其赋值,或赋值为None,则该属性为None,反应到数据库里就是<null>。
    对于不存在该属性的实体,是不能通过该属性来查找到该实体的(包括filter()和order()),因为它不会被编入索引。
    而属性为None的实体,可以与None做比较来查询,GQL中则使用NULL来表示。
  • 实体的key是一个路径,描述了实体的父实体类型、父实体键值、该实体类型和该实体键值,其中,父实体类型和父实体键值是可以同时不存在的(即无父元素,称为根实体)。
    一个app的所有实体的key都各不相同,但这是key的4个部分至少有一个不相同。也就是说,2个同类型的实体,如果它们的父实体不同,则它们的键值可以相同。
    实体一旦创建,key就不能被更改,因此更换一个父实体也是不允许的。
    键值有2种类型:正整数(id)和字符串(name)。
    当创建实体时未指定key_name和key时,数据库使用自动生成的id作为键值,该键值一般是向上增长,且不与已有key相等,但不保证连续性和递增性。
    若创建实体时指定了key_name,则使用字符串作为键值。该键值为unicode,支持中文,但此时参数必须为unicode类型(string类型会解码失败)。此外,该字符串现已可以以数字开头,Data Viewer里会用id=和name=来区分字符串类型的数字。
    若创建实体时指定了key,则不能同时使用parent或key_name参数。使用Key.from_path()创建该key对象时,可以将键值设为正整数(不能为0)。
  • 存储实体时,GAE不区分创建和更新操作。
    也就是说,如果建立了一个key,并用该key构造一个实体,再调用该实体的put()方法时:如果数据库中已有该key,则直接更新该key对应的实体;否则创建一个实体。
    如果是更新实体,数据库不会抛出创建失败的异常,这是习惯于使用关系型数据库需要注意的。
    若要避免手动设置的id正好被app自动分配到,可以用db.allocate_ids()函数来保留id。
    此外还有个Model.get_or_insert()方法来保证不更新同名实体,不过也可以自己用事务来实现。
  • 要保证数据的一致性时,或多或少会需要使用事务,而事务有一个很严重的限制:一个事务的所有操作只能针对一个实体组。
    这实际上是拿方便性和性能换一致性:实体是分布在多个数据库服务器之间的,如果同时操作2个不相关的实体,由于不需要同步,所以这种更新是可以并行进行的;而如果同时操作2个相关的实体,保证它们必须都更改或都不更改,则需要同步。而同步多个服务器是很麻烦的,所以GAE采用了实体组:将一组实体当成一个整体,保证对这个整体的操作是原子性的。
    然而采用了实体组,也就意味着不能同时对2个实体组进行操作,因为那需要同步2个实体组。如果同步2个实体组能够很方便地实现的话,Google就不会要求同步2个实体时需要处于同一实体组了。
    实体组导致的另一个限制还需要先介绍下事务的原理:当运行一个事务时,一旦获取了一个实体,数据库就会限制该实体。如果在事务运行期间,该实体发生了改变,则事务会收到通知,自动回滚并重新尝试,直到成功(未被其他通知打断)、达到最大重试次数或收到回滚以外的异常。
    也就是说,事务并不保证当前实体组以外的实体变化对其造成的影响。因此,假若允许在事务处理时使用WHERE查询,查出来的实体可能不是相同实体组的,尽管你不更新查出来的实体,也不能保证取出来的实体在整个事务处理过程中不变。所以Google禁止在事务处理时使用WHERE查询,不是因为技术上不能实现,而是因为这不能保证一致性,用户可能会误用。
  • 然而为了满足事务的运行条件,将大量实体放于一个实体组也是不明智的,因为那会导致对该实体组的操作会经常冲突,而需要回滚事务。
    对于更改不是太频繁的数据(例如用户名),目前我的做法是使用memcache。它是事务性的,且与数据库独立。所以只要在处理事务前在memcache里上一个定时30秒的锁,设置一个随机值;事务处理完后取出它,若锁不存在或值改变了,则回滚事务。
    当然,同时更改多个实体仍然必须使用实体组,但保证一个实体的多个属性则可以通过多个锁来实现,只是同步它们也是很头痛的。
  • 删除大量数据时,最高效的方法是查询时只取出key,然后用db.delete(keys)来删除。
    可以通过Model.all(keys_only=True)、Query(model_class, keys_only=True)来构造查询对象,也可在GQL查询时使用“SELECT __key__”。
  • 索引的更新是异步和并行的。当创建、更新和删除实体时,数据库API在完成对实体的操作后,需要等到索引更新完毕才会返回。

0条评论 你不来一发么↓

    想说点什么呢?