上次写了一个(上)之后,就接着不停的上传数据,最后终于将我的几个超级大的数据上传完毕,这也证明我的数据上传的方法是能够走通的,因此我再就几个需要注意的问题再补充一下:
一、续传的问题,这个问题至关重要,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对文件执行时间有限制,很容易超时;
考虑到以上几种限制,我做了一个通过不停刷新页面来实现删除表内容的程序,代码如下:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
data_cleantable.py
1
from google.appengine.ext import db
2
from google.appengine.ext import webapp
3
from google.appengine.ext.webapp.util import run_wsgi_app
4
from google.appengine.api import datastore_admin
5
import wsgiref.handlers
6
import urllib
7![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
8
class 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![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
19
class 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![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
43
def main():
44
application = webapp.WSGIApplication([
45
('/data_cleantable', CleanTable),
46
])
47![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
48
wsgiref.handlers.CGIHandler().run(application)
49![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
50![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
上面的页面每次删除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代码如下:
![](https://www.cnblogs.com/Images/OutliningIndicators/ContractedBlock.gif)
data_counttable.py
1
from google.appengine.ext import db
2
from google.appengine.ext import webapp
3
from google.appengine.ext.webapp.util import run_wsgi_app
4
from google.appengine.api import datastore_admin
5
from google.appengine.api.datastore_errors import Timeout
6
import wsgiref.handlers
7
import urllib
8![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
9
class 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![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
20
class 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![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
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![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
69
def main():
70
application = webapp.WSGIApplication([
71
('/data_counttable', CountTable),
72
])
73![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
74
wsgiref.handlers.CGIHandler().run(application)
75![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
76![](https://www.cnblogs.com/Images/OutliningIndicators/None.gif)
这个文件和上面的删除差不多,用法也一样,假如在计算的过程中,出现临时出错或者空白页的情况,只管刷新让其继续运行即可,也不会数错的。
虽然这个功能是每次数1000个,每个页面数10次,不过因为运行比较慢,要数100万的数据好事需要几分钟的,如果要进行改进,加快速度,应该可以采用fetch的offset参数,毕竟每次获取1000条数据却只读取最后一条还是挺浪费性能的,如果能使用q.fetch(1,999),想必应该会快很多,不过这样的话,就需要在返回结果为空的时候(说明数目不够1000条),重新查询fetch(1000)以得到剩余的记录总数,程序会复杂一点点,需要改进此程序速度的网友可以自己尝试一下。
我发现我上传的记录数比较多的表,数出来的数目总是对不上(总是略多一点),不知道是上传的时候多了,还是以上计算数目的程序算法有误,我希望上传了数据的网友来反馈一下。
到此,我关于数据上传的心得就介绍完毕,由于GAE的GQL支持的功能很少,想必后面的开发之后会遇到更多问题吧!