Оптимизация запросов с помощью индексов

Руслан Закиров

Best Practical Solutions, LLC

Индекс

Индекс — объект базы данных, создаваемый с целью повышения производительности выполнения запросов.

Пример 1 - простые условия

Выборка из одной таблицы

Один индекс?

users(id, email_address(unique), name, company, birth_date, disabled(boolean))
EXPLAIN SELECT * FROM u WHERE email = 'user6@g.com'
    AND company = 'g' AND name like 'nsg%' AND disabled = 0;
+...+-------+-------------------------+--------+...+------+...+
|...| type  | possible_keys           | key    |...| rows |...|
+...+-------+-------------------------+--------+...+------+...+
|...| const | iemail,iname,icomp,idis | iemail |...|    1 |...|
+...+-------+-------------------------+--------+...+------+...+

Как становятся избранным?

Пример выбора

EXPLAIN SELECT * FROM u WHERE company = 'g' AND name like 'nsg%';
EXPLAIN SELECT * FROM u WHERE company = 'g' AND name like 'n%';
+...+-------+---------------+-------+...+------+
|...| type  | possible_keys | key   |...| rows |
+...+-------+---------------+-------+...+------+
|...| range | iname,icomp   | iname |...|    1 |
+...+-------+---------------+-------+...+------+
|...| ref   | iname,icomp   | icomp |...|   42 |
+...+-------+---------------+-------+...+------+

Мне квадратный и красный

Проблема

Варианты

Составные индексы

CREATE INDEX icomp_iname ON users(company, name)

Плюсы

Составные индексы: (A,B) или (B,A)

CREATE INDEX iname_icomp ON users(name, company)
EXPLAIN SELECT * FROM u WHERE name = 'nsg';
EXPLAIN SELECT * FROM u WHERE name LIKE 'n%';
EXPLAIN SELECT * FROM u WHERE name = 'nsg' and company = 'g';
EXPLAIN SELECT * FROM u WHERE name = 'nsg' and company LIKE 'g%';
+...+-------------+---------+-------------+------+...+
|...| key         | key_len | ref         | rows |...|
+...+-------------+---------+-------------+------+...+
|...| iname_icomp |     255 | const       |    1 |...|
+...+-------------+---------+-------------+------+...+
|...| iname_icomp |     255 | NULL        |   27 |...|
+...+-------------+---------+-------------+------+...+
|...| iname_icomp |     510 | const,const |    1 |...|
+...+-------------+---------+-------------+------+...+
|...| iname_icomp |     510 | NULL        |    1 |...|
+...+-------------+---------+-------------+------+...+
но...

Составные индексы: (A,B) или (B,A) (...продолжение)

но...
EXPLAIN SELECT * FROM u WHERE name LIKE 'n%' and company = 'g';
+...+-------------+---------+-------------+------+...+
|...| key         | key_len | ref         | rows |...|
+...+-------------+---------+-------------+------+...+
|...| iname_icomp |     510 | NULL        |   27 |...|
+...+-------------+---------+-------------+------+...+
EXPLAIN SELECT * FROM u WHERE company = 'g';
+...+-------------+---------+-------------+------+...+
|...| NULL        |    NULL | NULL        | 1000 |...|
+...+-------------+---------+-------------+------+...+
Выход? Индекс по company, обратный составной или 5.x

Слияние индексов в MySQL 5.x

Как?

Что меняется?

Слияние(merge) vs. Составные индексы

Слияние

table users(email, name, company, town, ...)
index on (email)
index on (company)
...

Составные

table attributes(object_type, object_id, name, value)
index on (object_type, object_id, name)

Мне квадратный или красный

Проблема

Варианты

UNION вместо OR

Слияние(merge) vs. UNION "трюк"

Опять про уникальность

EXPLAIN SELECT * FROM u WHERE disabled = 0;
+...+------+---------------+------+...+------+...+
|...| type | possible_keys | key  |...| rows |...|
+...+------+---------------+------+...+------+...+
|...| ALL  | idis          | NULL |...|  750 |...|
+...+------+---------------+------+...+------+...+

Покрывающие индексы

Тип операции

Осталось только перечислить

EXPLAIN SELECT ...
+...+-------+...+
|...| type  |...|
+...+-------+...+
|...| const |...| - уникальный индекс, одна строка для всего запроса
+...+-------+...+
|...| ref   |...| - несколько строк
+...+-------+...+
|...| range |...| - диапазон
+...+-------+...+
|...| index |...| - сканирование индекса
+...+-------+...+
|...| ALL   |...| - сканирование таблицы
+...+-------+...+

Оптимизация сортировок

EXPLAIN SELECT... ORDER BY

EXPLAIN SELECT id FROM u WHERE company = 'g' ORDER BY name;
+...+-------------------+-------------+...+-----------------------------+
|...| possible_keys     | key         |...| Extra                       |
+...+-------------------+-------------+...+-----------------------------+
|...| icomp             | icomp       |...| Using where; Using filesort |
+...+-------------------+-------------+...+-----------------------------+
|...| icomp_iname,icomp | icomp_iname |...| Using where                 |
+...+-------------------+-------------+...+-----------------------------+

Как подсказать оптимизатору?

Задача френдленты

TABLE t(id, person, created, ...);
SELECT t.person FROM t WHERE person IN (.........)
ORDER BY created DESC LIMIT 25;

Задача френдленты (продолжение)

Получить все данные, отсортировать и выбрать 25

Задача френдленты (продолжение)

Выбирать по порядку, делать проверку и остановиться на 25

Задача френдленты (продолжение)

Задача френдленты (окончание)

Выборки из множества таблиц

Порядок выборки

Зависимые объединения

Перебор сочетаний

Пример

SELECT u.* FROM u, gm WHERE u.id = gm.user_id 
AND gm.group_id = 1 AND u.active = 1

Составные индексы

Задача о свойствах

o(id, ...), o2p(UNIQUE(object, property)), p(id, UNIQUE(name))

Найти объекты со свойствами foo, bar и zoo

  • SELECT FROM o JOIN o2p ON o.id = o2p.object JOIN p ON o2p.property = p.id
    WHERE p.name IN (...) GROUP BY o.id HAVING COUNT(1) = N ORDER BY NULL
  • SELECT o.id FROM o
        JOIN o2p AS o2p1 ON o.id = o2p1.object
          JOIN p AS p1   ON o2p1.property = p1.id AND p1.name = 'foo'
        JOIN o2p AS o2p2 ON o.id = o2p2.object
          JOIN p AS p2   ON o2p1.property = p2.id AND p2.name = 'bar'
    ...
    

Задача о свойствах (продолжение)

Задача о свойствах (продолжение)

SELECT STRAIGHT_JOIN o.id
FROM p p3, o2p o2p3, p p2, o2p o2p2, p p1, o2p o2p1, o
WHERE   o2p1.object = o.id        AND o2p1.property = p1.id
            AND p1.name = 'wind'
    AND o2p2.object = o2p1.object AND o2p2.property = p2.id
            AND p2.name = 'mysql'
    AND o2p3.object = o2p2.object AND o2p3.property = p3.id
            AND p3.name = 'cucumber'

Задача о свойствах (продолжение)

Неравномерное распределение

Задача о свойствах (окончание)

Отсортируем по популярности

SELECT p.name, count(o2p.object) AS count
FROM o2p JOIN p ON p.id = o2p.property GROUP BY p.name

Оптимизация сложных запросов

Вопросы?