Запрашивать (довольно) большие реляционные данные в Python за разумное время? - PullRequest
2 голосов
/ 14 марта 2012

У меня есть электронная таблица с 1,7 миллионами строк общим объемом 1 ГБ, и мне нужно выполнять различные запросы к ней. Будучи наиболее комфортным с Python, мой первый подход состоял в том, чтобы собрать кучу словарей, набранных таким образом, чтобы облегчить запросы, которые я пытался сделать. Например. если бы мне нужно было иметь доступ к каждому с определенным кодом города и возрастом, я бы сделал двумерный диктат areacode_age. Мне понадобилось немало таких, что увеличило объем моей памяти (порядка ~ 10 ГБ), и, хотя у меня было достаточно оперативной памяти для поддержки этого, процесс все еще был довольно медленным.

В этот момент мне показалось, что я играю в игру лоха. «Ну, для этого и созданы реляционные базы данных, верно?» - подумал я. Я импортировал sqlite3 и импортировал свои данные в базу данных в памяти. Я считаю, что базы данных созданы для скорости, и это решит мои проблемы.

Оказывается, однако, что выполнение запроса типа «SELECT (a, b, c) ОТ foo WHERE date1 <= d AND date2> e AND name = f» занимает 0,05 секунды. Выполнение этого для моих строк длиной 1,7 м заняло бы 24 часа вычислительного времени. Мой хакерский подход со словарями был примерно на 3 порядка быстрее для этой конкретной задачи (и, в этом примере, я не мог явно указать дату1 и дату2, поэтому я получал каждую строку с соответствующим именем и затем фильтровал по дате).

Итак, мой вопрос: почему это так медленно, и как я могу сделать это быстро? И что такое Pythonic подход? Возможности, которые я рассматривал:

  • sqlite3 слишком медленный, и мне нужно что-то более тяжелое
  • Мне нужно как-то изменить свою схему или свои запросы, чтобы они были более ... оптимизированы?
  • подходы, которые я до сих пор пробовал, совершенно неверны, и мне нужен какой-то совершенно новый инструмент
  • Я где-то читал, что в sqlite 3 повторные вызовы cursor.execute выполняются намного медленнее, чем при использовании cursor.executemany. Оказывается, что executemany даже не совместимо с операторами select, поэтому я думаю, что это была красная сельдь.

Спасибо.

Ответы [ 3 ]

4 голосов
/ 14 марта 2012

sqlite3 слишком медленный, и мне нужно что-то более тяжелое

Во-первых, sqlite3 работает быстро, иногда быстрее, чем MySQL

Во-вторых, вы должны использовать индекс,добавление составного индекса (date1, date2, name) значительно ускорит процесс

2 голосов
/ 14 марта 2012

Оказывается, что выполнение запроса типа "SELECT (a, b, c) FROM foo ГДЕ date1 <= d AND date2> e AND name = f "занимает 0,05 секунды. это для моих 1,7-м строк заняло бы 24 часа вычислительного времени. Мой хакер подход со словарями был примерно на 3 порядка быстрее эта конкретная задача (и, в этом примере, я не мог ввести дату1 и date2, очевидно, поэтому я получал каждую строку, которая соответствует имени и затем фильтрация по дате).

Вы на самом деле попробовали и заметили, что это заняло 24 часа? Время обработки не обязательно прямо пропорционально размеру данных.

И вы предлагаете запустить SELECT (a, b, c) FROM foo WHERE date1<=d AND date2>e AND name=f 1,7 миллиона раз? Вам нужно только запустить его один раз, и он вернет все подмножество строк, соответствующих вашему запросу.

1,7 миллиона строк - это не мало, но, конечно, это не проблема для базы данных, полностью хранящейся в памяти на вашем локальном компьютере. (Нет медленного доступа к диску; нет медленного доступа к сети.)


Доказательство в пудинге. Для меня это довольно быстро (большую часть времени тратится на генерацию ~ 10 миллионов случайных чисел.)

import sqlite3, random

conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE numbers (a FLOAT, b FLOAT, c FLOAT, d FLOAT, e FLOAT, f FLOAT)");
for _ in xrange(1700000):
    data = [ random.random() for _ in xrange(6) ];
    conn.execute("INSERT INTO numbers VALUES (?,?,?,?,?,?)", data)

conn.commit()

print "done generating random numbers"

results = conn.execute("SELECT * FROM numbers WHERE a > 0.5 AND b < 0.5")
accumulator = 0
for row in results:
    accumulator += row[0]

print ("Sum of column `a` where a > 0.5 and b < 0.5 is %f" % accumulator)

Редактировать: Хорошо, так что вам действительно нужно выполнить это 1,7 миллиона раз.

В этом случае вам, вероятно, понадобится index . Процитирую Википедию: Индекс базы данных:

Индекс базы данных - это структура данных, которая улучшает скорость передачи данных. операции поиска в таблице базы данных за счет более медленных записей и увеличенное место для хранения. Индексы могут быть созданы с использованием одного или нескольких столбцы таблицы базы данных, обеспечивая основу для быстрого случайного поиск и эффективный доступ к заказанным записям.

Вы бы сделали что-то вроде CREATE INDEX dates_and_name ON foo(date1,date2,name), а затем (как мне кажется) выполнили бы остальные ваши SELECT операторы как обычно. Попробуйте и посмотрите, не ускоряет ли это процесс.

0 голосов
/ 14 марта 2012

Поскольку вы уже говорите на SQL, самый простой подход будет:

  1. Поместите все ваши данные в таблицу MySQL. Он должен хорошо работать на 1,7 миллиона строк.
  2. Добавьте нужные индексы, проверьте настройки, убедитесь, что он будет быстро работать на большом столе .
  3. Доступ к нему из Python
  4. ...
  5. Profit!
...