Как вы делаете Python / PostgreSQL быстрее? - PullRequest
6 голосов
/ 26 сентября 2008

Прямо сейчас у меня есть анализатор журнала, который просматривает 515 МБ текстовых файлов (файл за каждый день за последние 4 года). Мой код в настоящее время выглядит так: http://gist.github.com/12978. Я использовал psyco (как видно из кода), и я также компилирую его и использую скомпилированную версию. Это делает около 100 строк каждые 0,3 секунды. Машина представляет собой стандартный 15-дюймовый MacBook Pro (2,4 ГГц C2D, 2 ГБ ОЗУ)

Возможно ли, чтобы это происходило быстрее или это ограничение на язык / базу данных?

Ответы [ 5 ]

8 голосов
/ 26 сентября 2008

Не тратьте время на профилирование. Время всегда в операциях с базой данных. Делайте как можно меньше. Просто минимальное количество вставок.

Три вещи.

One. Не выбирайте снова и снова, чтобы соответствовать измерениям Date, Hostname и Person. Получить все данные ОДИН РАЗ в словарь Python и использовать их в памяти. Не делайте повторных синглтонов. Используйте Python.

Два. Не обновлять.

В частности, не делайте этого. Это плохой код по двум причинам.

cursor.execute("UPDATE people SET chats_count = chats_count + 1 WHERE id = '%s'" % person_id)

Его можно заменить простым SELECT COUNT (*) FROM .... Никогда не обновлять, чтобы увеличить счет. Просто посчитайте строки, которые есть, с помощью инструкции SELECT. [Если вы не можете сделать это с помощью простого SELECT COUNT или SELECT COUNT (DISTINCT), вам не хватает некоторых данных - ваша модель данных должна всегда обеспечивать правильные полные значения. Никогда не обновлять.]

А. Никогда не создавайте SQL, используя подстановку строк. Полностью тупой.

Если по какой-то причине SELECT COUNT(*) недостаточно быстрый (сначала тест, прежде чем делать что-то нехорошее), вы можете кэшировать результат подсчета в другой таблице. ПОСЛЕ всех нагрузок. Сделайте SELECT COUNT(*) FROM whatever GROUP BY whatever и вставьте это в таблицу счетов. Не обновлять Когда-либо.

Три. Используйте переменные связывания. Всегда.

cursor.execute( "INSERT INTO ... VALUES( %(x)s, %(y)s, %(z)s )", {'x':person_id, 'y':time_to_string(time), 'z':channel,} )

SQL никогда не меняется. Значения связаны в изменении, но SQL никогда не меняется. Это НАМНОГО быстрее. Никогда не создавайте операторы SQL динамически. Никогда.

3 голосов
/ 26 сентября 2008

В цикле for вы многократно вставляете в таблицу 'chats', поэтому вам нужно всего лишь один SQL-оператор с переменными связывания, который будет выполняться с разными значениями. Таким образом, вы можете поместить это перед циклом for:

insert_statement="""
    INSERT INTO chats(person_id, message_type, created_at, channel)
    VALUES(:person_id,:message_type,:created_at,:channel)
"""

Затем вместо каждого выполняемого вами оператора SQL поместите это на место:

cursor.execute(insert_statement, person_id='person',message_type='msg',created_at=some_date, channel=3)

Это заставит вещи работать быстрее, потому что:

  1. Объекту курсора не придется каждый раз повторять оператор
  2. Сервер БД не должен будет генерировать новый план выполнения, поскольку он может использовать тот, который был создан ранее.
  3. Вам не придется вызывать santitize (), поскольку специальные символы в переменных привязки не будут частью выполняемого оператора sql.

Примечание. Синтаксис переменной привязки, который я использовал, зависит от Oracle. Вам придется проверить документацию библиотеки psycopg2 для точного синтаксиса.

Другие оптимизации:

  1. Вы увеличиваете с "UPDATE people SET chatscount" после каждой итерации цикла. Сохраните словарь, отображающий пользователя на chat_count, а затем выполните оператор общего числа, которое вы видели. Это будет быстрее, чем попадание в БД после каждой записи.
  2. Используйте переменные связывания для ВСЕХ ваших запросов. Не только оператор вставки, я выбрал это в качестве примера.
  3. Измените все функции find _ * (), которые делают просмотр базы данных, чтобы кэшировать свои результаты, чтобы им не приходилось каждый раз нажимать на базу данных.
  4. psycho оптимизирует программы на python, которые выполняют большое количество числовых операций. Скрипт дорогой, а не дорогой, поэтому я не ожидаю, что он даст вам много, если будет какая-либо оптимизация.
3 голосов
/ 26 сентября 2008

Используйте переменные связывания вместо литеральных значений в операторах sql и создайте курсор для каждый уникальный SQL-оператор, так что нет необходимости повторного анализа оператора при следующем его использовании. Из питона db api doc:

Подготовить и выполнить базу данных операция (запрос или команда). Параметры могут быть представлены в виде последовательности или отображение и будет связан с переменные в операции. переменные указаны в базе данных обозначение (см. paramstyle модуля) атрибут для деталей). [5]

Ссылка на операцию будет удерживается курсором. Если то же самое объект операции передается снова, тогда курсор может оптимизировать его поведение. Это наиболее эффективно для алгоритмы, где та же операция используется, но разные параметры привязан к нему (много раз).

ВСЕГДА ВСЕГДА используйте переменные связывания.

2 голосов
/ 26 сентября 2008

Как предложил Марк, используйте переменные привязки. База данных должна подготовить каждый оператор только один раз, а затем «заполнить пробелы» для каждого выполнения. Как приятный побочный эффект, он автоматически позаботится о проблемах с цитированием строк (которые ваша программа не обрабатывает).

Включите транзакции (если их еще нет) и выполните один коммит в конце программы. База данных не должна будет ничего записывать на диск, пока все данные не будут зафиксированы. И если ваша программа обнаружит ошибку, ни одна из строк не будет зафиксирована, что позволит вам просто перезапустить программу после устранения проблемы.

Ваши функции log_hostname, log_person и log_date выполняют ненужные операции SELECT для таблиц. Сделайте соответствующие атрибуты таблицы PRIMARY KEY или UNIQUE. Затем, вместо того, чтобы проверять наличие ключа перед вставкой, просто сделайте INSERT. Если человек / дата / имя хоста уже существует, INSERT завершится с ошибкой ограничения. (Это не будет работать, если вы используете транзакцию с одной фиксацией, как предложено выше.)

В качестве альтернативы, если вы знаете, что вы только ВСТАВЛЯЕТЕ в таблицы во время работы вашей программы, тогда создайте параллельные структуры данных в памяти и сохраняйте их в памяти, пока вы выполняете свои ВСТАВКИ. Например, прочитайте все имена хостов из таблицы в ассоциативный массив в начале программы. Если вы хотите знать, нужно ли делать INSERT, просто выполните поиск в массиве. Если запись не найдена, выполните INSERT и обновите массив соответствующим образом. (Это предложение совместимо с транзакциями и единичным коммитом, но требует больше программирования. Однако это будет намного быстрее.)

1 голос
/ 26 сентября 2008

В дополнение ко многим прекрасным предложениям, которые дал @Mark Roddy, сделайте следующее:

  • не используйте readlines, вы можете перебирать файловые объекты
  • попробуйте использовать executemany вместо execute: попробуйте делать пакетные вставки, а не отдельные вставки, это происходит быстрее, потому что меньше затрат Это также уменьшает количество коммитов
  • str.rstrip будет работать просто отлично вместо удаления новой строки с регулярным выражением

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...