在 python 中读取巨大 MySQL 表的最快方法
Posted
技术标签:
【中文标题】在 python 中读取巨大 MySQL 表的最快方法【英文标题】:Fastest way to read huge MySQL table in python 【发布时间】:2018-12-11 15:02:21 【问题描述】:我试图读取由数百万行组成的非常巨大的 mysql 表。我使用过Pandas
库和chunks
。请看下面的代码:
import pandas as pd
import numpy as np
import pymysql.cursors
connection = pymysql.connect(user='xxx', password='xxx', database='xxx', host='xxx')
try:
with connection.cursor() as cursor:
query = "SELECT * FROM example_table;"
chunks=[]
for chunk in pd.read_sql(query, connection, chunksize = 1000):
chunks.append(chunk)
#print(len(chunks))
result = pd.concat(chunks, ignore_index=True)
#print(type(result))
#print(result)
finally:
print("Done!")
connection.close()
如果我限制要选择的行数,实际上执行时间是可以接受的。但如果还想选择最少的数据(例如 100 万行),则执行时间会显着增加。
也许有更好/更快的方法从 python 中的关系数据库中选择数据?
【问题讨论】:
***.com/questions/34180448/…这看起来和你的问题很相似 【参考方案1】:您可以尝试使用不同的 mysql 连接器。我建议尝试mysqlclient
,这是最快的 mysql 连接器(我相信有相当大的优势)。
pymysql
是一个纯 python mysql 客户端,而mysqlclient
是(更快的)C 库的包装器。
用法和pymsql
基本一样:
import MySQLdb
connection = MySQLdb.connect(user='xxx', password='xxx', database='xxx', host='xxx')
在此处阅读有关不同连接器的更多信息:What's the difference between MySQLdb, mysqlclient and MySQL connector/Python?
【讨论】:
感谢@djmac 的建议。我会试一试。我可以考虑什么来加快阅读过程的总体速度?也许这个块解决方案没有那么好优化? 老实说,我认为这主要取决于数据库结构(和数据)很多。如果您真的在整个表上执行SELECT *
,那么不同的数据结构(即不是 MySQL)可能会更好?否则,确保您拥有正确的索引会产生很大的不同(EXPLAIN
声明可以帮助您并提供有关良好调整的指导)
好的。我对pandas
的内部结构不太熟悉——但是 MySQL-python 连接器(mysqlclient 所基于)比 pymysql 快 1000%(根据这个wiki.openstack.org/wiki/PyMySQL_evaluation)。
另一种选择是考虑并行化(例如,使用 multiprocessing
模块和工作人员池)。例如,您可以让多个工作人员选择较小的块并将它们与pd.concat
拼接在一起。您甚至可以对数据库进行分片(例如,垂直分区)并获得更好的性能 - 尽管如果瓶颈是 pandas
,那可能就不那么划算了。
当然 - 将其作为另一个答案【参考方案2】:
另一种选择可能是使用multiprocessing
模块,将查询拆分并将其发送到多个并行进程,然后将结果连接起来。
如果不了解 pandas
分块 - 我认为您将不得不手动进行分块(这取决于数据)...不要使用 LIMIT / OFFSET - 性能会很糟糕。
这可能不是一个好主意,具体取决于数据。如果有一种有用的方法来拆分查询(例如,如果它是一个时间序列,或者有某种适当的索引列可以使用,它可能是有意义的)。我在下面放了两个例子来展示不同的情况。
示例 1
import pandas as pd
import MySQLdb
def worker(y):
#where y is value in an indexed column, e.g. a category
connection = MySQLdb.connect(user='xxx', password='xxx', database='xxx', host='xxx')
query = "SELECT * FROM example_table WHERE col_x = 0".format(y)
return pd.read_sql(query, connection)
p = multiprocessing.Pool(processes=10)
#(or however many process you want to allocate)
data = p.map(worker, [y for y in col_x_categories])
#assuming there is a reasonable number of categories in an indexed col_x
p.close()
results = pd.concat(data)
示例 2
import pandas as pd
import MySQLdb
import datetime
def worker(a,b):
#where a and b are timestamps
connection = MySQLdb.connect(user='xxx', password='xxx', database='xxx', host='xxx')
query = "SELECT * FROM example_table WHERE x >= 0 AND x < 1".format(a,b)
return pd.read_sql(query, connection)
p = multiprocessing.Pool(processes=10)
#(or however many process you want to allocate)
date_range = pd.date_range(start=d1, end=d2, freq="A-JAN")
# this arbitrary here, and will depend on your data /knowing your data before hand (ie. d1, d2 and an appropriate freq to use)
date_pairs = list(zip(date_range, date_range[1:]))
data = p.map(worker, date_pairs)
p.close()
results = pd.concat(data)
这可能是更好的方法(并且没有经过适当的测试等)。如果您尝试一下,有兴趣知道它是怎么回事。
【讨论】:
我想我还没有完全理解这些例子是如何工作的......让我们说第一个。您为列表 col_x_categories 的每个项目映射并调用函数工作者。但是我们在哪里定义 y 和 col_x_categories?而且我认为这个映射操作对于 n 个进程是并行的。【参考方案3】:对于那些使用 Windows 并且无法安装 MySQLdb 的用户。我正在使用这种方式从大表中获取数据。
import mysql.connector
i = 1
limit = 1000
while True:
sql = "SELECT * FROM super_table LIMIT , ".format(i, limit)
cursor.execute(sql)
rows = self.cursor.fetchall()
if not len(rows): # break the loop when no more rows
print("Done!")
break
for row in rows: # do something with results
print(row)
i += limit
【讨论】:
以后会很慢。因为“限制”。如果使用“限制”,则将进行全表扫描。 “加入”将是一个适当的解决方案。像这样 ''' "SELECT id, xValue, yValue, zValue, createdAt FROM (SELECT id FROM Position LIMIT 2000000, 1000) q JOIN Position p ON p.id = q.id" '''以上是关于在 python 中读取巨大 MySQL 表的最快方法的主要内容,如果未能解决你的问题,请参考以下文章