从 MySQL 将数值数据加载到 python/pandas/numpy 数组中的最快方法
Posted
技术标签:
【中文标题】从 MySQL 将数值数据加载到 python/pandas/numpy 数组中的最快方法【英文标题】:Fastest way to load numeric data into python/pandas/numpy array from MySQL 【发布时间】:2014-04-06 02:25:55 【问题描述】:我想从 mysql 表中读取一些数字(双精度,即 float64)数据。数据的大小约为 200k 行。
MATLAB 参考:
tic;
feature accel off;
conn = database(...);
c=fetch(exec(conn,'select x,y from TABLENAME'));
cell2mat(c.data);
toc
经过的时间约为 1 秒。
在 python 中做同样的事情,使用这里的几个例子(我都试过了,即使用 pandas read_frame、frame_query 和 __processCursor 函数): How to convert SQL Query result to PANDAS Data Structure?
参考python代码:
import pyodbc
import pandas.io.sql as psql
import pandas
connection_info = "DRIVER=MySQL ODBC 3.51 \
Driver;SERVER=;DATABASE=;USER=;PASSWORD=;OPTION=3;"
cnxn = pyodbc.connect(connection_info)
cursor = cnxn.cursor()
sql = "select x,y from TABLENAME"
#cursor.execute(sql)
#dataframe = __processCursor(cursor, dataframe=True)
#df = psql.frame_query(sql, cnxn, coerce_float=False)
df = psql.read_frame(sql, cnxn)
cnxn.close()
大约需要 6 秒。 Profiler 说所有花费的时间都在 read_frame 中。 我想知道是否有人可以给我一些提示,这将如何加速以至少匹配 MATLAB 代码。如果这在 python 中是可能的。
编辑:
瓶颈似乎在 cursor.execute (在 pymysql 库中)或 cursor.fetchall() 在 pyodbc 库中。最慢的部分是逐个元素(逐行、逐列)读取返回的 MySQL 数据并将其转换为之前由同一库推断的数据类型。
到目前为止,我已经设法通过这个非常肮脏的解决方案将其加速到接近 MATLAB:
import pymysql
import numpy
conn = pymysql.connect(host='', port=, user='', passwd='', db='')
cursor = conn.cursor()
cursor.execute("select x,y from TABLENAME")
rez = cursor.fetchall()
resarray = numpy.array(map(float,rez))
finalres = resarray.reshape((resarray.size/2,2))
上面的 cur.execute 不是 pymysql 执行的! 我已经在文件“connections.py”中修改了它。首先,函数 def _read_rowdata_packet,现在改为:
rows.append(self._read_row_from_packet(packet))
替换为
self._read_string_from_packet(rows,packet)
这里 _read_string_from_packet 是 _read_row_from_packet 的简化版,代码如下:
def _read_string_from_packet(self, rows, packet):
for field in self.fields:
data = packet.read_length_coded_string()
rows.append(data)
这是一个超级肮脏的解决方案,可将加速时间从 6 秒缩短到 2.5 秒。我想知道,是否可以通过使用不同的库/传递一些参数来避免所有这些?
因此解决方案是批量读取整个 MySQL 对字符串列表的回复,然后批量类型转换为数字数据类型,而不是逐个元素地进行。 python中是否已经存在类似的东西?
【问题讨论】:
***.com/questions/7061824/… 上的答案建议使用fetchall
,后跟np.fromiter
(可能会进行一些调整)。
感谢 hpaulj,我已经测试过了 - 与其他方法相同(慢)的速度。我认为的问题是将数据从网络导入本机 python 数据类型。因此,在 MySQLdb 和 pymysql 中,这都发生在 cursor.execute() 中,如果我理解正确,这是一个不正确的实现,因为在简单地执行 SQL 语句后,不应将任何内容“提取”到本机数据类型中。
【参考方案1】:
“问题”似乎是 MySQLdb、pymysql 和 pyodbc 对数据所做的从 MySQL 的十进制类型到 python 的十进制类型的类型转换。通过将 MySQLdb 中的 converters.py 文件(在最后几行)更改为:
conversions[FIELD_TYPE.DECIMAL] = float
conversions[FIELD_TYPE.NEWDECIMAL] = float
而不是十进制。十进制似乎完全解决了这个问题,现在下面的代码:
import MySQLdb
import numpy
import time
t = time.time()
conn = MySQLdb.connect(host='',...)
curs = conn.cursor()
curs.execute("select x,y from TABLENAME")
data = numpy.array(curs.fetchall(),dtype=float)
print(time.time()-t)
运行不到一秒! 有趣的是,十进制。十进制似乎从来都不是分析器中的问题。
类似的解决方案应该在 pymysql 包中工作。 pyodbc 比较棘手:它都是用 C++ 编写的,因此您必须重新编译整个包。
更新
这里是一个不需要修改 MySQLdb 源代码的解决方案: Python MySQLdb returns datetime.date and decimal 然后将数字数据加载到熊猫中的解决方案:
import MySQLdb
import pandas.io.sql as psql
from MySQLdb.converters import conversions
from MySQLdb.constants import FIELD_TYPE
conversions[FIELD_TYPE.DECIMAL] = float
conversions[FIELD_TYPE.NEWDECIMAL] = float
conn = MySQLdb.connect(host='',user='',passwd='',db='')
sql = "select * from NUMERICTABLE"
df = psql.read_frame(sql, conn)
在加载 200k x 9 表时比 MATLAB 高出约 4 倍!
【讨论】:
我猜您的字段被定义为 DECIMAL(或 NUMERIC),而不是 FLOAT 或 DOUBLE。转换器使用float
作为默认值,除非decimal
可用。 decimal
是纯 Python,所以会很慢。分析器可能看不到转换器调用,因为它们是由 _mysql
中的编译代码执行的。【参考方案2】:
还可以使用turbodbc 包查看这种处理方式。要将您的结果集转换为 NumPy 数组的 OrderedDict,只需执行以下操作:
import turbodbc
connection = turbodbc.connect(dsn="My data source name")
cursor = connection.cursor()
cursor.execute("SELECT 42")
results = cursor.fetchallnumpy()
将这些结果转换为数据集需要额外的几毫秒。我不知道 MySQL 的加速比,但我已经看到其他数据库的 10 倍。
加速主要是通过使用批量操作而不是逐行操作来实现的。
【讨论】:
以上是关于从 MySQL 将数值数据加载到 python/pandas/numpy 数组中的最快方法的主要内容,如果未能解决你的问题,请参考以下文章
将数据从 MySQL 加载到 HDFS 时出现 Sqoop 错误