2016-11-03 18 views
1

У меня очень длительное время выполнения (в отличие от длинных выборок) с использованием Python MySQLdb для извлечения данных из большой таблицы, и я хотел бы понять, что что-то явно не так.Python MySQLdb execute slow

Моя таблица определяется следующим образом:

create table mytable(
    a varchar(3), 
    b bigint, 
    c int, 
    d int, 
    e datetime, 
    f varchar(20), 
    g varchar(10), 
    primary key(a, b, c, d)) 
ENGINE=InnoDB; 

В настоящее время она содержит 150 миллионов строк, а оценка размера таблицы равна 19.

Питон код выглядит следующим образом:

import MySQLdb 
database = MySQLdb.connect(passwd="x", host="dbserver", user="user", db="database", port=9999) 
mysql_query = """select a, b, c, d, e, f, g from mytable use index (primary) where a = %s order by a, b, c, d""" 
mysql_cursor = database.cursor() 
mysql_cursor.execute(mysql_query, ["AA"]) 
for a, b, c, d, e, f, g in mysql_cursor: 
    #Do something 

Мой сюрприз пришел из времени, затрачиваемого на команды execute. Здесь скучно, но я ожидал, что execute потратит почти не время (поскольку он должен пересекать таблицу с использованием первичного ключа) и довольно долгое время провел в цикле for.

Explain, план выглядит следующим образом:

explain select a, b, c, d, e, f, g from mytable use index (primary) where a = %s order by a, b, c, d 
'1','SIMPLE','eventindex','ref','PRIMARY','PRIMARY','5','const','87402369','Using where' 

На данный момент, все строки содержат одинаковое значение в столбце а (я имел в виду, чтобы добавить другие значения позже, но в данный момент распределение колонки контент на самом деле не сбалансирован). Колонка b лучше распределена.

Что может объяснить, что MySQL тратит столько времени на выполнение запроса (в отличие от того, чтобы тратить время на выборку строк)?

Bonus вопрос. Любой очевидный быстрый выигрыш для оптимизации этого варианта использования? Разделение таблицы на столбец b? Столбец a? Удаление столбца a и использование вместо него отдельных таблиц?

ответ

0

На самом деле выглядит как вопрос MySQL на самом деле - я не думаю, что проблема связана с Python или mysql-python.

WRT/SQL материал: индекс, который не достаточно селективным (слишком много подобных значений) может быть весьма вредным, так как вы в конечном итоге делает последовательное сканирование в дополнение к индексу обходе дерева - и на самом деле гораздо больше доступа к диску, чем с обычным сканированием таблицы -, поэтому вы теряете с обеих сторон (IOW: вы получаете только накладные расходы на обход индекса, но ни одна из его преимуществ). Вы найдете более подробно об этом здесь: MySQL: low cardinality/selectivity columns = how to index? и здесь Role of selectivity in index scan/seek

В вашем случае вы можете попробовать запрос без оговорки use index, и, возможно, даже заставить оптимизатор простого обхода индекса с использованием ignore index clause вместо этого.

0

Посмотрев, похоже, что это нормальное поведение с MySQL. Из разных источников выглядит, что большая часть работы выбора выполняется на этапе выполнения для MySQL, и во время выборки происходит только передача сети. Я потратил так много времени на Oracle (где выполнение обычно практически ничего не имеет на практике, а мясо обработки происходит во время выборки), что я не понимал, что MySQL может вести себя по-другому.

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

import MySQLdb 
database = MySQLdb.connect(passwd="x", host="dbserver", user="user", db="database", port=9999) 

def get_next_item(database): #Definition of this generator encapsulating the paging system 
    first_call = True 
    mysql_cursor = database.cursor() 
    nothing_more_found = False 
    while not nothing_more_found: 
     mysql_query = """select a, b, c, d, e, f, g from mytable use index (primary) 
      where a = %s order by a, b, c, d 
      limit 10000""" if first_call else """select a, b, c, d, e, f, g from mytable use index (primary) 
      where a = %s and ((b > %s) or (b = %s and c > %s) or (b = %s and c = %s and d > %s)) 
      order by a, b, c, d 
      limit 10000""" 

     if first_call: 
      mysql_cursor.execute(mysql_query, ["AA", last_b, last_b, last_c, last_b, last_c, last_d]) 
      first_call = False 
     else: 
      mysql_cursor.execute(mysql_query, ["AA"]) 
     if mysql_cursor.rowcount == 0: 
      nothing_more_found = True 
     for a, b, c, d, e, f, g in mysql_cursor: 
      yield (a, b, c, d, e, f, g) 
      last_b, last_c, last_d = b, c, d 

for a, b, c, d, e, f, g in get_next_item(database): #Usage of the generator 
    #Do something 

Объяснение на MySQL выполнить против выборки в этом post от Майка Lischke.

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

С другой стороны, для Oracle во время выбора большая часть действий происходит во время извлечения. Это объясняется самим here

думать об этом так

1 Том Кайта) синтаксический - очень хорошо определено, что prepareStatement - мы делаем мягкий или жесткий разбор, составить заявление, выяснить как выполнить это.

2) выполнить - мы ОТКРЫВАЕМ заявление. Для обновления, для удаления, для вставка - это было бы, когда вы ОТКРЫВАЕТ инструкцию, мы выполняем ее . Вся работа здесь происходит.

для выбора более сложный. Большинство выборок будут выполнять ZERO во время выполнения выполнения. Все, что мы делаем, это открыть курсор - курсор является указателем на пространство в общем пуле, где находится план, ваши значения привязки , SCN, который представляет «время» для вашего запроса - короче курсор в этой точке - ваш контекст, ваше состояние виртуальной машины , подумайте о плане SQL, как будто это байт-код (он) выполнен как программа (она есть) на виртуальной машине (она есть). Курсор - это указатель на инструкцию (где вы находитесь в выполнении этого заявления), ваше состояние (например, регистры) и т. Д. Обычно ничего не делает здесь - он просто «готовится к рок-н-роллу, программа готова к работе, но еще не началась ».

Однако есть исключения во всем - включить трассировку и сделать выбрать * из scott.emp ДЛЯ ОБНОВЛЕНИЯ. Это выбор, но это также обновление. Вы увидите работу, выполненную во время выполнения, а также фазу выборки . Работа, выполненная во время выполнения, заключалась в том, чтобы выйти из и прикоснуться к каждой строке и заблокировать ее. Работа, выполненная во время фазы ввода , заключалась в том, что выходить и получать данные обратно к клиенту .

3) fetch - здесь мы видим почти всю работу для SELECTS (и ничего не для другого DMLS, поскольку вы не извлекаете из обновления ).

Существует два способа обработки SELECT. То, что я называю «быстрый обратный запрос» и «медленное возвращение запрос»

http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:275215756923#39255764276301

отрывок из эффективного Oracle по Design описывающее это в глубине, но достаточно сказать, запрос вида:

выбрать * from one_billion_row_table;

не будет копировать данные в любом месте, не нужно будет обращаться к последней строке , прежде чем возвращать первую строку. Мы просто прочитали бы данные, как вы получите из блоков, в которых он находится.

Однако запрос вида:

выберите * от one_billion_row_table заказа по unindexed_column;

, что мы, вероятно, придется прочитать последнюю строку перед возвратом первой строки (после последней строки чтения вполне может быть первый ряд вернулся!), И мы должны были бы скопировать что-нибудь (Темп, сортировки область пространство) сначала.

В случае первого запроса, если вы:

проанализирован его (мало работы синтаксический) открыл (не реальный мир, просто получить готов) принес 1 строку и закрыл его

вы бы см. ОЧЕНЬ небольшую работу, выполненную в фазе выборки, мы должны были только прочитать один блок, возможно, чтобы вернуть первую запись.

Однако сделать те же шаги против второго запроса, и вы увидите выборку из одной строки сделать TON работы - так как мы должны найти последней строки перед первым может быть возвращены.