Самый быстрый способ загрузки числовых данных в массив python/pandas/numpy из MySQL
Я хочу прочитать некоторые числовые (double, i.e. float64) данные из таблицы MySQL. Размер данных составляет ~ 200 тыс. Строк.
Ссылка 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):
Как преобразовать результат SQL-запроса в pandas Структура данных?
Справочный код 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 секунд. Профилер говорит, что все время проведенное было в read_frame.
Мне было интересно, может ли кто-нибудь дать мне несколько советов, как можно ускорить, по крайней мере, совпадение с кодом MATLAB. И если это вообще возможно в python.
EDIT:
Узкое место, кажется, находится внутри курсора .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 НЕ ПИМИСЛК EXECUTE! Я изменил его внутри файла "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?
Ответы
Ответ 1
"Проблема", по-видимому, была преобразованием типа, которое происходит от десятичного типа MySQL до десятичного десятичного кода. Десятичный результат. Для данных MySQLdb, pymysql и pyodbc. Изменив файл converters.py(в последних строках) в MySQLdb, получим:
conversions[FIELD_TYPE.DECIMAL] = float
conversions[FIELD_TYPE.NEWDECIMAL] = float
вместо decimal.Decimal, кажется, полностью решает проблему и теперь следующий код:
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)
Работает менее чем за секунду!
Что смешно, decimal.Decimal никогда не было проблемой в профилировщике.
Аналогичное решение должно работать в пакете pymysql. pyodbc более сложный: все написано на С++, поэтому вам придется перекомпилировать весь пакет.
UPDATE
Вот решение, не требующее изменения исходного кода MySQLdb:
Python MySQLdb возвращает datetime.date и decimal
Затем решение для загрузки числовых данных в pandas:
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)
Убит MATLAB в 4 раза при загрузке таблицы 200k x 9!
Ответ 2
Также проверьте этот способ выполнения действий с помощью пакета turbodbc. Чтобы преобразовать ваш результирующий набор в массивы OrderedDict из NumPy, просто выполните следующее:
import turbodbc
connection = turbodbc.connect(dsn="My data source name")
cursor = connection.cursor()
cursor.execute("SELECT 42")
results = cursor.fetchallnumpy()
Преобразование этих результатов в набор данных должно потребовать несколько дополнительных миллисекунд. Я не знаю ускорение для MySQL, но я видел фактор 10 для других баз данных.
Ускорение в основном достигается за счет использования массовых операций, а не для действий по строке.