Быстрая вставка записей в таблицу с помощью SQLAlchemy - PullRequest
5 голосов
/ 21 мая 2010

Я анализирую журнал и вставляю его в MySQL или SQLite, используя SQLAlchemy и Python. Прямо сейчас я открываю соединение с БД и, перебирая каждую строку, вставляю ее после анализа (сейчас это только одна большая таблица, не очень опытная в SQL). Затем я закрываю соединение, когда цикл завершен. Суммированный код:

log_table = schema.Table('log_table', metadata,
                         schema.Column('id', types.Integer, primary_key=True),
                         schema.Column('time', types.DateTime),
                         schema.Column('ip', types.String(length=15))
....
engine = create_engine(...)
metadata.bind = engine
connection = engine.connect()
....
for line in file_to_parse:
    m = line_regex.match(line)
    if m:
        fields = m.groupdict()
        pythonified = pythoninfy_log(fields) #Turn them into ints, datatimes, etc
        if use_sql:
            ins = log_table.insert(values=pythonified)
            connection.execute(ins)
            parsed += 1

Мои два вопроса:

  • Есть ли способ ускорить вставки в этой базовой структуре? Может быть, есть Очередь вставок и некоторые потоки вставок, какие-то массовые вставки и т. Д.?
  • Когда я использовал MySQL, для ~ 1,2 миллиона записей время вставки составляло 15 минут. С SQLite время вставки составляло чуть более часа. Ощущается ли разница во времени между двигателями БД правильной или означает, что я делаю что-то очень неправильное?

Ответы [ 3 ]

4 голосов
/ 21 мая 2010

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

3 голосов
/ 21 мая 2010

Я сделал следующее, чтобы добиться некоторого дозирования:

inserts = []
insert_every = 1000
for line in file_to_parse:
    m = line_regex.match(line)
    if m:
        fields = m.groupdict()
        if use_sql: #This uses Globals, Ick :-/
            inserts.append(pythonified)
            if (parsed % insert_every) == 0:
                connection.execute(log_table.insert(), inserts)
                inserts = []
            parsed += 1
if use_sql:
    if len(inserts) > 0:
        connection.execute(log_table.insert(), inserts)

Это не использует транзакции, но очень ленивым образом позволило мне переключить этап вставки / разбора с ~ 13 секунд до ~ 2 секунд с бэкэндом mysql, используя меньший образец. Я увижу разницу между mysql и sqlite с этим изменением, используя полный образец.

Я нашел основную информацию для этого здесь .

Результаты:
Движок: не сгруппированное время вставки в минутах: сгруппированное время вставки в минутах
Sqlite: 61: 8
MySql: 15: 2,5

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

3 голосов
/ 21 мая 2010

Не зная механизм таблиц (MyISAM? InnoDB?), Схему и индексы, сложно комментировать специфику между двумя базами данных, которые вы там используете.

Однако при использовании MySQL подобным образом вы, вероятно, обнаружите, что гораздо быстрее записать ваши данные во временный текстовый файл, а затем использовать синтаксис LOAD DATA INFILE , чтобы загрузить все это в ваш база данных. Похоже, вы можете вызвать метод execute для вашего объекта подключения , чтобы запустить SQL, необходимый для этого.

Кроме того, если вы не можете добавлять строки за строкой и каждый раз воссоздаете таблицу, вы можете проверить ключевые ограничения в своей программе и добавить эти ограничения только после того, как все строки были вставлены, сохранив БД время выполнения проверок ограничений на каждую вставку.

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