将大量数据从远程服务器拉入DataFrame

时间:2014-09-02 23:17:59

标签: python postgresql pandas psycopg2

为了尽可能多地提供上下文,我试图将存储在远程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_sqlread_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)服务器提取大量数据的正确策略是什么?

3 个答案:

答案 0 :(得分:4)

我怀疑这里有几个(相关的)事情导致缓慢:

  1. read_sql是用python编写的,所以它有点慢(特别是与read_csv相比,它是用cython编写的 - 并且为了速度而精心实施!)并且它依赖于sqlalchemy而不是某些(可能)更快)C-DBAPI。 转向sqlalchmey的动力是在未来使这一举措更容易(以及跨SQL平台支持)。
  2. 由于内存中有太多python对象,这可能会耗尽内存(这与不使用C-DBAPI有关),但可能会被解决...
  3. 我认为直接的解决方案是基于块的方法(并且feature request可以在pandas read_sqlread_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库!

导入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()