2013-09-06 3 views
1

Я пытаюсь сократить время выполнения этого скрипта. Он запрашивает базу данных о 2 миллиона записей о 1000 раз по петле: (! Около 800 секунд)Оптимизация запросов MySQL PDO

foreach ($ids as $id){ 
    $stmt=$dbh->query("SELECT SQL_CACHE * FROM `ids` 
       WHERE $id BETWEEN `id_start` AND `id_end`"); 
    $rows[] = $stmt->fetch(); 
} 

Он принимает навсегда на 4 ядра 8 Гб машине. Группы идентификаторов не пересекаются, иды в каждом исполнении имеют только несколько разных групп, и я проиндексировал оба (id_start, id_end) и (id_end).

Кэширование значительно улучшает ситуацию (запуск одного и того же значения 1000 раз больше, чем один раз - всего несколько секунд), но я хотел бы знать, что я могу сделать для ускорения не кэшированных запросов.

Пример вывода из EXPLAIN:

"id" "select_type" "table"  "type" "possible_keys"  "key"    "key_len" "ref" "rows" "Extra" 
"1"  "SIMPLE"  "ids"  "range" "id_start,id_end" "id_start,id_end" "5"   ""  "52508" "Using index condition" 

EDIT: Вместо «Использование индекса состояния» я получаю «Использование где» иногда (не уверен, но я думаю, что из идентификатор значения выше, чем 840771583) Почему?

EDIT 2: Полный создать код:

CREATE TABLE `ids` (
    `id_start` INT(10) UNSIGNED NULL DEFAULT NULL, 
    `id_end` INT(10) UNSIGNED NULL DEFAULT NULL, 
    `iso-639-1` VARCHAR(2) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci', 
    `country_name` VARCHAR(64) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci', 
    `region_name` VARCHAR(64) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci', 
    `city_name` VARCHAR(64) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci', 
    `area_code` VARCHAR(16) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci', 
    `timezone` VARCHAR(6) NULL DEFAULT NULL COLLATE 'utf8_unicode_ci', 
    UNIQUE INDEX `id_startid_end` (`id_start`, `id_end`), 
    INDEX `id_end` (`id_end`), 
    INDEX `country_name` (`country_name`), 
    INDEX `region_name` (`region_name`), 
    INDEX `city_name` (`city_name`), 
    INDEX `area_code` (`area_code`), 
    INDEX `iso-639-1` (`iso-639-1`), 
    INDEX `timezone` (`timezone`) 
) 
COLLATE='utf8_unicode_ci' 
ENGINE=InnoDB; 
+0

Что 'EXPLAIN' сказать о запросах? Использует ли он индексы? – Barmar

+0

@Barmar Да, похоже. Я отредактировал, чтобы добавить вывод 'EXPLAIN'. – NotGaeL

+0

Эта таблица является таблицей только для чтения? Предполагая «Разделение чтения/записи». И какой механизм хранения он использует? – Drazzi

ответ

4

Поскольку интервалы не перекрываются, попробуйте это переписывание запроса:

SELECT * 
FROM ids 
WHERE id_start = 
     (SELECT MAX(id_start) 
     FROM ids 
     WHERE id_start <= $id 
    ) 
    AND $id <= id_end ; 
+0

WHOA !!! Волшебство !!! Только 1 секунда для 1000 запросов! Как ты сделал это? Что тут происходит? – NotGaeL

+1

Извините, не так много времени сегодня. Я могу расширить ответ с объяснением позже сегодня или завтра. –

+0

После небольшого просмотра ссылки, опубликованной в вашем первом комментарии, я вижу, что в исходном запросе всего 52508 рассмотрены строки (строки в диапазоне, я думаю) из всего 1822141 строк таблицы. В запросе, который вы предложили, используется индекс fseek, который быстрее, чем последовательный индекс fscan, когда есть только небольшое количество строк для проверки, потому что, несмотря на то, что медленнее при чтении смежных строк, он может пропускать строки. Я прав? Я еще не понял, почему перекрытие важно, но я прокладываю себе путь к нему :-) – NotGaeL

1
It queries a database with about 2 million records about 1000 times on a loop: 
                 ^^^^^^^^^^^^^^^^^^^^ 

ЗДЕСЬ

ваша проблема.

Должен быть единый запрос наверняка.

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

Этот вопрос не имеет ничего общего с PDO, кстати. Когда вы сталкиваетесь с проблемой, вы должны сузить ее как можно больше, вынимая ВСЕ ненужные части. Говоря о запросе, вы должны взять его в консоль и поиграть там.

+0

Я указал, что использовал PDO только в том случае, если драйвер может обеспечить некоторую форму оптимизации (например, подготовленные инструкции, которые я пробовал, но ничего не улучшил). Конечно, я работаю с консолями. Я попытаюсь сформировать большие запросы, группируя несколько идентификаторов, но вывод должен быть упорядочен по идентификатору, поэтому я не знаю, будет ли это работать намного лучше ... – NotGaeL

+0

Используя только один запрос, требуется немного меньше времени, даже без его упорядочивания более 5 минут для запроса с 1000 запросами, и каким-то образом я получаю менее 1000 строк, поэтому запрос работает некорректно (я использую тот же запрос, но с 'WHERE $ id BETWEEN id_start И id_end OR $ id2 BETWEEEN id_start И id_end' и т. Д.). Я также увеличил размер пула буферов innodb без везения, вероятно, из-за того, что этот пост говорит о том, что небольшие базы данных уже полностью кэшированы: http://lists.mysql.com/mysql/214401 – NotGaeL

Смежные вопросы