Google App Engine:如何修改你的数据模型

导言

如果你有一个成功的GAE应用, 不可避免的你会要修改你的数据库架构. 本文通过一个小例子介绍了修改数据库架构的两个基本步骤:

  1. 更新数据模型类定义
  2. 更新Datastore中的已有数据实体(这一步并不是总是必要的, 下面会讲什么时候你需要这样做)。

开始之前

在更新你的数据模型时,可能需要暂时禁止用户在你的应用中更新数据。 是否确实需要取决于你的应用, 但是在某些情况下, 暂时禁止用户输入会大大便于你更新已有数据。

更新你的数据模型

这里有一个例子,一个简单的图片:

classPicture(db.Model):   
author = db.UserProperty()  
png_data = db.BlobProperty()  
name = db.StringProperty(default='') # 唯一的
的图片名

我们来修改这个model, 为每个图片加上评分。为了保存评分, 我们保存用户评分的次数和评价得分值。更新这个数据模型很容易,我们只是增加两个新的属性:

classPicture(db.Model):
  author = db.UserProperty()
  png_data = db.BlobProperty()
  name = db.StringProperty(default='') # Unique name.
  num_votes = db.IntegerProperty(default=0)
  avg_rating = db.FloatProperty(default=0)

现在所有保存到Datastore的新建实体将获得一个默认的评分为0 。请注意,Datastore中现有的数据并没有自动被修改, 所以他们不会有这些属性。

更新现有数据实体

App Engine datastore并不要求所有数据有相同的一组属性。 更新你的数据模型之后,现有的数据实体将继续没有这些属性。 在某些情况下,这就够了,你不需要做什么。 那什么时候你想更新现有的数据,使他们也有新的属性? 一种情况是当你想要对新的属性做查询。在我们这个Picture的例子中, 查询"最受欢迎"或者"最不受欢迎"图片不会返回更新之前的数据, 因为它们没有相应的评分属性。要解决此问题,我们需要更新Datastore中现有的数据实体。

从概念上来说,更新现有的数据实体很容易。你只需要创建request handler, 取出每个实体,设置新属性的值, 然后保存数据。 有两个我们必须要解决的问题:

  • 查询返回数据集有1000条的上限。如果有多于1000条记录,需要多次查询以获得所有记录。
  • GAE要求http request 在很短的时间内必须返回,否则request 会超时。如果有很多数据,request handler不能在一个请求中处理完所有数据
解决方法是在一个 request 中只更新一小部分数据。通过使多个 request,我们更新所有的数据,而不超过查询上限和request超时限制。为简单起见, 我们在一个request中只更新一条数据,象这样:
  1. 读取一个数据实体
  2. 设置属性的值(如果属性有缺省值,会自动设置)
  3. 保存数据
  4. 用meta refresh标记,让浏览器访问更新下一个数据的URL

一句警告:写读取查询时,应避免使用OFFSET(它不适合大数据集)而是使用WHERE语句量限制返回的数据数量。如果你的数据已经有某种唯一值的属性,这很容易。 在这个例子中,图片名称(name)是唯一的,所以我们对name将使用WHERE语句。

代码如下:

# Request handler for the URL /update_datastore
def get(self):
  name = self.request.get('name',None)
  if name isNone:
    # First request, just get the first name out of the datastore.
    pic = models.Picture.gql('ORDER BY name DESC').get()
    name = pic.name

  q = models.Picture.gql('WHERE name <= :1 ORDER BY name DESC', name)
  pics = q.fetch(limit=2)
  current_pic = pics[0]
  if len(pics)==2:
    next_name = pics[1].name
    next_url ='/update_datastore?name=%s'% urllib.quote(next_name)
  else:
    next_name ='FINISHED'
    next_url ='/' # Finished processing, go back to main page.
  # In this example, the default values of 0 for num_votes and avg_rating are
  # acceptable, so we don't need to do anything other than call put().

  current_pic.put()

  context ={
    'current_name': name,
    'next_name': next_name,
    'next_url': next_url,
  }
  self.response.out.write(template.render('update_datastore.html', context))

相应的template显示我们正在更新哪条记录, 并用meta refresh 自动转到下条记录:


<html>
<head>
  <metahttp-equiv="refresh"content="0;url={{ next_url }}"/>
</head>
<body>
  <h3>Update Datastore</h3>
  <ul>
    <li>Updated: {{ current_name }}</li>
    <li>About to update: {{ next_name }}</li>
  </ul>
</body>
</html>

如果你的数据中没有具有唯一值的属性,上面的例子不能直接使用(当某个属性值对应很多条记录时, 它会很慢) 。你需要扩展它以能够处理这种情况。但是概念是相同的,使用WHERE限制查询返回的数据集数目以分批更新数据,然后逐条保持。

Datastore中移除已删除的属性

如果你从数据模型中移除了一个属性,你会发现现有的实体仍然有这个属性。它仍然会显示在管理控制台(admin console)中,并仍然存在于Datastore众。 要真正清理旧数据,你需要的遍历每条记录并逐条移除属性值。

  1. 确认你已从数据模型定义删除了属性。
  2. 如果你的数据模型类继承自db.model ,它改为继承自db.expando 。(db.model实例无法动态修改,这是我们在下一步要做的) 。
  3. 遍历现有的所有数据(如上文所述) 。 对每条数据,使用delattr删除属性,然后保存数据。
  4. 如果你的数据模型原本继承自db.model ,不要忘了改回去。

以后

这种多次request的处理方法现在是可行的。 不过现在我们正在做一些离线处理的解决方法。当这些成为可用的,可能会提供一个更合理的方式来修改你的数据,而且不用加重服务器的负担。你可以考虑订阅我的blog,跟踪最新的进展

你可能感兴趣的:(Google)