优化 Netcdf 到 Python 代码
我有一个Python脚本,它可以读取netcdf文件,并把气候数据一行一行地插入到PostgreSQL数据库的表里。这样做当然非常慢,所以我想找办法来优化这个代码。
我在考虑做一个很大的列表,然后使用复制命令来一次性插入数据。不过,我不太确定具体该怎么操作。还有一种方法是先把数据写入一个CSV文件,然后再用PostgreSQL的COPY命令把这个CSV文件导入到数据库里。我觉得这样可能比一行一行插入要快。
如果你有任何优化的建议,我会非常感激。这个netcdf文件可以在这里找到(不过需要注册): http://badc.nerc.ac.uk/browse/badc/cru/data/cru_ts/cru_ts_3.21/data/pre
# NetCDF to PostGreSQL database
# CRU-TS 3.21 precipitation and temperature data. From NetCDF to database table
# Requires Python2.6, Postgresql, Psycopg2, Scipy
# Tested using Vista 64bit.
# Import modules
import psycopg2, time, datetime
from scipy.io import netcdf
# Establish connection
db1 = psycopg2.connect("host=192.168.1.162 dbname=dbname user=username password=password")
cur = db1.cursor()
### Create Table
print str(time.ctime())+ " Creating precip table."
cur.execute("DROP TABLE IF EXISTS precip;")
cur.execute("CREATE TABLE precip (gid serial PRIMARY KEY not null, year int, month int, lon decimal, lat decimal, pre decimal);")
### Read netcdf file
f = netcdf.netcdf_file('/home/username/output/project_v2/inputdata/precipitation/cru_ts3.21.1901.2012.pre.dat.nc', 'r')
##
### Create lathash
print str(time.ctime())+ " Looping through lat coords."
temp = f.variables['lat'].data.tolist()
lathash = {}
for entry in temp:
print str(entry)
lathash[temp.index(entry)] = entry
##
### Create lonhash
print str(time.ctime())+ " Looping through long coords."
temp = f.variables['lon'].data.tolist()
lonhash = {}
for entry in temp:
print str(entry)
lonhash[temp.index(entry)] = entry
##
### Loop through every observation. Set timedimension and lat and long observations.
for _month in xrange(1344):
if _month < 528:
print(str(_month))
print("Not yet")
else:
thisyear = int((_month)/12+1901)
thismonth = ((_month) % 12)+1
thisdate = datetime.date(thisyear,thismonth, 1)
print(str(thisdate))
_time = int(_month)
for _lon in xrange(720):
for _lat in xrange(360):
data = [int(thisyear), int(thismonth), lonhash[_lon], lathash[_lat], f.variables[('pre')].data[_time, _lat, _lon]]
cur.execute("INSERT INTO precip (year, month, lon, lat, pre) VALUES "+str(tuple(data))+";")
db1.commit()
cur.execute("CREATE INDEX idx_precip ON precip USING btree(year, month, lon, lat, pre);")
cur.execute("ALTER TABLE precip ADD COLUMN geom geometry;")
cur.execute("UPDATE precip SET geom = ST_SetSRID(ST_Point(lon,lat), 4326);")
cur.execute("CREATE INDEX idx_precip_geom ON precip USING gist(geom);")
db1.commit()
cur.close()
db1.close()
print str(time.ctime())+ " Done!"
2 个回答
我遇到过类似的需求,所以我把Numpy数组重新写成了PostgreSQL的二进制输入文件格式。主要的问题是,目标表的所有列都需要插入,这样就会变得有些复杂,特别是当你需要编码几何数据WKB的时候。不过,你可以使用一个临时的未记录表,把netCDF文件加载进去,然后再把这些数据选入另一个具有正确几何类型的表中。
可以使用 psycopg2 的 copy_from
方法。
这个方法需要一个像文件一样的对象,但你可以自己创建一个类来读取和处理输入文件,并通过 read()
和 readlines()
方法按需返回内容。
如果你不太确定怎么做,你可以像你说的那样,生成一个 CSV 临时文件,然后用 COPY
命令来处理它。为了获得最佳性能,你可以生成 CSV 文件(Python 的 csv
模块很有用),然后把它复制到服务器上,使用服务器端的 COPY thetable FROM '/local/path/to/file'
,这样就可以避免网络延迟。
大多数情况下,使用 copy ... from stdin
通过像 psql 的 \copy
或 psycopg2 的 copy_from
这样的方式会更简单,而且速度也足够快。特别是如果你结合使用 Python 的 multiprocessing
模块进行生产者/消费者模式(其实并没有听起来那么复杂),这样你的输入解析代码就不会在数据库写入数据时被卡住。
想要获取更多关于加快批量加载速度的建议,可以查看 如何提高 PostgreSQL 的插入性能 - 不过我看到你已经在做一些正确的事情,比如在最后创建索引和将工作批量处理成事务。