Самый быстрый способ загрузки числовых данных в массив 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 для других баз данных.

Ускорение в основном достигается за счет использования массовых операций, а не для действий по строке.