上次写了一个(上)之后,就接着不停的上传数据,最后终于将我的几个超级大的数据上传完毕,这也证明我的数据上传的方法是能够走通的,因此我再就几个需要注意的问题再补充一下:
一、续传的问题,这个问题至关重要,Google的文章上建议使用“Sqlite”来完成,我开始的时候安装了Sqlite,却始终没有搞明白怎么才能让它断点续传,最后我只好放弃了Sqlite,按照下面这个方式手工续传:
1.使用"--num_threads=1"参数将线程数更改为1,省得被这些线程搞乱了;
2.每次上传之后,通过上传的结果报告,将前面成功的行删除,然后再接着传;
这个方法实在是笨的不能再笨了,其实,只要指定db_filename参数(而且不能指定为skip)即可使用Sqlite来断点续传,我原先以为不需要指定这个参数就可以自动断点续传,后来发现如果不使用这个参数,会使用时间戳作为每次上传进度文件的文件名,这样每次都是采用不同的进度文件,也就不可能接着上次的来继续传了。
后来再上传数据的时候,我就采用了这个db_filename参数,就方便多了,也可以采用多线程,快了很多,不过需要注意的是,我觉得默认的num_threads参数太大(10),上传经常出问题,改成5就差不多了,不过这也可能是我这边的网速的原因。
二、数据清除的问题,有时候上传的数据有问题,或者有的表不再需要,想删除所有的数据(或者根据一个查询删除部分数据),怎么办?GAE的管理界面没有提供这样的功能,只能自己做,如果自己做,会有如下障碍:
1.GQL之中没有删除语句,只能查询出结果集,然后删除;
2.GQL每次最个查询最多返回1000条结果;
3.Appspot对文件执行时间有限制,很容易超时;
考虑到以上几种限制,我做了一个通过不停刷新页面来实现删除表内容的程序,代码如下:
data_cleantable.py
1from google.appengine.ext import db
2from google.appengine.ext import webapp
3from google.appengine.ext.webapp.util import run_wsgi_app
4from google.appengine.api import datastore_admin
5import wsgiref.handlers
6import urllib
7
8class Train_stations(db.Model):
9 name = db.StringProperty()
10 latlng = db.GeoPtProperty()
11 superior = db.StringProperty()
12 address = db.StringProperty()
13 postcode = db.StringProperty()
14 regionCode = db.StringProperty()
15 level = db.StringProperty()
16 telephone = db.StringProperty()
17 oldName = db.StringProperty()
18
19class CleanTable(webapp.RequestHandler):
20 def get(self):
21 sql = self.request.get('sql')
22 if not sql:
23 self.response.out.write('<html><head></head><body><form action="?" method="get" onsubmit="return window.confirm(\'Really?\')">SQL:<input type="text" size="40" name="sql"/><input type="submit" value="Delete"></form><p>By K_Reverter <a href="http://step1.appspot.com">http://step1.appspot.com</a></p><body></html>')
24 return
25 q = db.GqlQuery(sql)
26 self.response.headers['Content-Type'] = 'text/html'
27 self.response.out.write('<html><head><META HTTP-EQUIV="Cache-Control" CONTENT="no-cache"><meta http-equiv="Refresh" content="1;url=?sql='+urllib.quote(sql)+'"></head><body>')
28 self.response.out.write('<p><strong>'+sql+'</strong></p>');
29 try:
30 for i in range(10):
31 results = q.fetch(100)
32 if len(results)==0:
33 self.response.out.write("<p>OK</p>")
34 self.response.out.write('<script language="javascript">self.location=\'?\'</script>')
35 break
36 db.delete(results)
37 self.response.out.write("<p>"+str(len(results))+" removed</p>")
38 except Exception, inst:
39 self.response.out.write(str(inst))
40 self.response.out.write('<p>By K_Reverter <a href="http://step1.appspot.com">http://step1.appspot.com</a></p>')
41 self.response.out.write('</body></html>')
42
43def main():
44 application = webapp.WSGIApplication([
45 ('/data_cleantable', CleanTable),
46 ])
47
48 wsgiref.handlers.CGIHandler().run(application)
49
50
上面的页面每次删除100条结果,为什么不用最大的1000条呢?因为一次查询1000条并删除要的时间比较长,可能会遭遇Google的页面运行时间限制,因此,每次只删除100条;
很遗憾的是,必须手工将每个要操作的表的定义添加到这个文件之中,和上面的Train_stations一样,因此,你如果要使用此代码,可能要将你需要操作的所有数据表的定义加入到此文件之中,否则会得到一个"No implementation for kind *"的错误,本来我很试试调用"datastore_admin"来实现自动的调用数据表定义的,可是最终也没有成功,因此只好作罢。
你可以将上面的代码部署到自己的Appspot里面运行,不过要记得在"app.yaml"之中注册此程序,并且设置为"login: admin",否则可能会出现安全问题。
三、数据记录数的问题,通常我们上传了记录都应该知道当前系统之中记录究竟有多少条,因为我对Google使用的bigtable的机制不是很清楚,我不知道Google为什么不提供此功能,我们还是要自己来做这个功能,和删除的功能差不多,一样会遇到以下问题:
1.GQL之中没有Count语句,你只能逐条的查询出来,才能数出数目
2.GQL每次最个查询最多返回1000条结果;
3.Appspot对文件执行时间有限制,很容易超时;
因此,大体上仿照上面的代码,进行实现的CountTable代码如下:
data_counttable.py
1from google.appengine.ext import db
2from google.appengine.ext import webapp
3from google.appengine.ext.webapp.util import run_wsgi_app
4from google.appengine.api import datastore_admin
5from google.appengine.api.datastore_errors import Timeout
6import wsgiref.handlers
7import urllib
8
9class Train_stations(db.Model):
10 name = db.StringProperty()
11 latlng = db.GeoPtProperty()
12 superior = db.StringProperty()
13 address = db.StringProperty()
14 postcode = db.StringProperty()
15 regionCode = db.StringProperty()
16 level = db.StringProperty()
17 telephone = db.StringProperty()
18 oldName = db.StringProperty()
19
20class CountTable(webapp.RequestHandler):
21 def get(self):
22 sql = self.request.get('sql')
23 if not sql:
24 self.response.out.write('<html><head></head><body><form action="?" method="get" onsubmit="return window.confirm(\'Start?\')">SQL:<input type="text" size="40" name="sql"/><input type="submit" value="Count"></form><p>By K_Reverter <a href="http://step1.appspot.com">http://step1.appspot.com</a></p><body></html>')
25 return
26 number=0
27 if(self.request.get('number')):
28 number=int(self.request.get('number'))
29 key=self.request.get('key')
30
31 self.response.headers['Content-Type'] = 'text/html'
32 #replace yourapp and YouData your app info below.
33 self.response.out.write('<html><head></head><body>')
34 self.response.out.write('<p><strong>'+sql+'</strong></p>');
35 finished=False
36 error=False
37 try:
38 for i in range(10):
39 qSql=sql
40 if key:
41 if qSql.find('where')<0:
42 qSql+=" where "
43 else:
44 qSql+=" and "
45 qSql+=" __key__ > KEY('%s')" %(key)
46 qSql+=" order by __key__ "
47 q = db.GqlQuery(qSql.encode("ascii"))
48 results = q.fetch(1000)
49 if len(results)==0:
50 self.response.out.write("<p>OK</p>")
51 finished=True
52 break
53 number+=len(results)
54 key=str(results[len(results)-1].key())
55 self.response.out.write("<p>"+str(number)+" found</p>")
56 except Timeout:
57 error=False
58 except Exception, inst:
59 error=True
60 self.response.out.write(str(inst))
61 if not error:
62 if finished:
63 self.response.out.write('<script language="javascript">alert(\'Count Complete(%d)!\');self.location=\'?\'</script>' % (number))
64 else:
65 self.response.out.write('<script language="javascript">self.location=\'?sql=%s&number=%d&key=%s\'</script>' % (urllib.quote(sql),number,key))
66 self.response.out.write('<p>By K_Reverter <a href="http://step1.appspot.com">http://step1.appspot.com</a></p>')
67 self.response.out.write('</body></html>')
68
69def main():
70 application = webapp.WSGIApplication([
71 ('/data_counttable', CountTable),
72 ])
73
74 wsgiref.handlers.CGIHandler().run(application)
75
76
这个文件和上面的删除差不多,用法也一样,假如在计算的过程中,出现临时出错或者空白页的情况,只管刷新让其继续运行即可,也不会数错的。
虽然这个功能是每次数1000个,每个页面数10次,不过因为运行比较慢,要数100万的数据好事需要几分钟的,如果要进行改进,加快速度,应该可以采用fetch的offset参数,毕竟每次获取1000条数据却只读取最后一条还是挺浪费性能的,如果能使用q.fetch(1,999),想必应该会快很多,不过这样的话,就需要在返回结果为空的时候(说明数目不够1000条),重新查询fetch(1000)以得到剩余的记录总数,程序会复杂一点点,需要改进此程序速度的网友可以自己尝试一下。
我发现我上传的记录数比较多的表,数出来的数目总是对不上(总是略多一点),不知道是上传的时候多了,还是以上计算数目的程序算法有误,我希望上传了数据的网友来反馈一下。
到此,我关于数据上传的心得就介绍完毕,由于GAE的GQL支持的功能很少,想必后面的开发之后会遇到更多问题吧!