为了尽可能多地提供上下文,我试图将存储在远程postgres服务器(heroku)上的一些数据拉入pandas DataFrame,使用psycopg2进行连接。
我对两个特定的表感兴趣,用户和事件,并且连接正常,因为在下拉用户数据时
import pandas.io.sql as sql
# [...]
users = sql.read_sql("SELECT * FROM users", conn)
等待几秒钟后,DataFrame按预期返回。
<class 'pandas.core.frame.DataFrame'>
Int64Index: 67458 entries, 0 to 67457
Data columns (total 35 columns): [...]
然而,当试图直接从ipython中提取更大,更重的事件数据时,经过很长一段时间,它只会崩溃:
In [11]: events = sql.read_sql("SELECT * FROM events", conn)
vagrant@data-science-toolbox:~$
当从iPython笔记本尝试时,我得到了 Dead kernel 错误
内核已经死了,你想重新启动吗?如果不重新启动内核,则可以保存笔记本,但在重新打开笔记本之前,运行代码将无法运行。
更新#1:
为了更好地了解我想要引入的 events 表的大小,这里是记录的数量和每个的属性数量:
In [11]: sql.read_sql("SELECT count(*) FROM events", conn)
Out[11]:
count
0 2711453
In [12]: len(sql.read_sql("SELECT * FROM events LIMIT 1", conn).columns)
Out[12]: 18
更新#2:
内存绝对是当前read_sql
实现的瓶颈:当拉下事件并尝试运行另一个iPython实例时,结果是
vagrant@data-science-toolbox:~$ sudo ipython
-bash: fork: Cannot allocate memory
更新#3:
我首先尝试使用read_sql_chunked
实现,只返回部分DataFrames数组:
def read_sql_chunked(query, conn, nrows, chunksize=1000):
start = 0
dfs = []
while start < nrows:
df = pd.read_sql("%s LIMIT %s OFFSET %s" % (query, chunksize, start), conn)
start += chunksize
dfs.append(df)
print "Events added: %s to %s of %s" % (start-chunksize, start, nrows)
# print "concatenating dfs"
return dfs
event_dfs = read_sql_chunked("SELECT * FROM events", conn, events_count, 100000)
并且运行良好,但在尝试连接DataFrame时,内核会再次死亡 这是在给VM 2GB RAM之后。
基于Andy对read_sql
与read_csv
实施和性能差异的解释,接下来我尝试将记录附加到CSV中,然后将它们全部读入DataFrame:
event_dfs[0].to_csv(path+'new_events.csv', encoding='utf-8')
for df in event_dfs[1:]:
df.to_csv(path+'new_events.csv', mode='a', header=False, encoding='utf-8')
再次,写入CSV成功完成 - 一个657MB的文件 - 但从CSV读取永远不会完成。
如何估计一个657MB的CSV文件就足以读取多少RAM,因为2GB似乎还不够?
感觉我错过了对DataFrames或psycopg2的一些基本理解,但我陷入了困境,我甚至无法确定瓶颈或优化的位置。
从远程(postgres)服务器提取大量数据的正确策略是什么?
答案 0 :(得分:4)
我怀疑这里有几个(相关的)事情导致缓慢:
read_sql
是用python编写的,所以它有点慢(特别是与read_csv
相比,它是用cython编写的 - 并且为了速度而精心实施!)并且它依赖于sqlalchemy而不是某些(可能)更快)C-DBAPI。 转向sqlalchmey的动力是在未来使这一举措更容易(以及跨SQL平台支持)。 我认为直接的解决方案是基于块的方法(并且feature request可以在pandas read_sql
和read_sql_table
中本地使用此工作。
编辑:从Pandas v0.16.2开始,这种基于块的方法在read_sql
中原生实现。
由于您使用的是postgres,因此您可以访问LIMIT and OFFSET queries,这使得分块变得非常简单。 (我认为这些语言并不适用于所有sql语言吗?)
首先,获取表格中的行数(或estimate):
nrows = con.execute('SELECT count(*) FROM users').fetchone()[0] # also works with an sqlalchemy engine
使用它来遍历表(为了调试你可以添加一些打印语句以确认它正在工作/没有崩溃!)然后结合结果:
def read_sql_chunked(query, con, nrows, chunksize=1000):
start = 1
dfs = [] # Note: could probably make this neater with a generator/for loop
while start < nrows:
df = pd.read_sql("%s LIMIT %s OFFSET %s" % (query, chunksize, start), con)
dfs.append(df)
return pd.concat(dfs, ignore_index=True)
注意:这假设数据库适合内存!如果不是,你需要处理每个块(mapreduce样式)......或投入更多内存!
答案 1 :(得分:0)
尝试使用pandas:
mysql_cn = mysql.connector.connect(host='localhost', port=123, user='xyz', passwd='****', db='xy_db')**
data= pd.read_sql('SELECT * FROM table;', con=mysql_cn)
mysql_cn.close()
它对我有用。
答案 2 :(得分:0)
这是一个基本的光标示例,可能会有所帮助:
导入psycopg2
导入psycopg2.extras
导入系统
def main(): conn_string =“主机='本地主机'dbname ='my_database'用户='postgres'密码='秘密'” ###打印我们将用于连接的连接字符串
conn = psycopg2.connect(conn_string)
### HERE IS THE IMPORTANT PART, by specifying a name for the cursor
### psycopg2 creates a server-side cursor, which prevents all of the
### records from being downloaded at once from the server.
cursor = conn.cursor('cursor_unique_name', cursor_factory=psycopg2.extras.DictCursor)
cursor.execute('SELECT * FROM my_table LIMIT 1000')
### Because cursor objects are iterable we can just call 'for - in' on
### the cursor object and the cursor will automatically advance itself
### each iteration.
### This loop should run 1000 times, assuming there are at least 1000
### records in 'my_table'
row_count = 0
for row in cursor:
row_count += 1
print "row: %s %s\n" % (row_count, row)
如果名称 ==“ 主要”: main()