Оптимизация заспросов индексами
Author: Руслан Закиров

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

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

#### Best Practical Solutions, LLC

# Индекс

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

- Операции
  - Основные: =, >, <
  - Производные: >=, <=, LIKE, BETWEEN, IN, ORDER BY...
- Свойства
  - Правило левой части (LIKE 'xxx' → LIKE 'xxx%' → LIKE '%xxx')
  - Избирательность (unique → boolean)
  - Длинна, Глубина, Сбалансированность...

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

-     users(id, email_address(unique), name, company, birth_date)

- Каждое поле проиндексировано в отдельности
- email_address = 'ruz@bestpractical.com'
- company = 'Best Practical Solutions'
- name LIKE 'Ruslan%'
- ...но не name LIKE '%, Ruslan'
- birth_date BETWEEN '1980-01-01' AND '1981-01-01'
- ORDER BY birth_date

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

- Один индекс :( (5.x++)
- Все в одной куче: WHERE, ORDER BY, GROUP BY...
- Индексы нужно оценить и выбрать
- X = 1 AND Y = 2 - четыре вариант индексирования (5.x++)
- X = 1 OR Y = 2 - нельзя использовать индексы совсем (5.x++)
- X IN (1,2) - приходиться сканировать диапазон

# Один индекс?

    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 |...|
    +...+-------+-------------------------+--------+...+------+...+

- possible_keys - выбор индексов
- keys - индекс, который будет использован
- type - способ получения данных
- rows - приблизительное количество строк

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

- Правила
  - + Быстрое получение плана
  - - Исключения из правил
- Стоимость
  - + Взвешенные действия
  - - Медленное получение плана

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

    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 |
    +...+-------+---------------+-------+...+------+

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

## Проблема

-     X = 1 AND Y = 2

-     SELECT * FROM users WHERE company = 'BPS' AND name = 'Ruslan';

## Варианты

- Выбрать индекс по одной из колонок
- Выбрать два набора и найти пересечение (5.x++)
- Объединить колонки в одном индексе

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

    CREATE INDEX icomp_iname ON users(company, name)

## Плюсы

- + Более уникальные наборы
- +/- Подходит для большего количества операций
- - Последовательность: (A,B) != (B,A)
- - Длинна индекса

# Составные индексы: (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)

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

## Проблема

-     X = 1 OR Y = 2

-     SELECT * FROM objs WHERE shape = 'square' OR color = 'red';

- Редко. Бывает. Денормализация(phone1,phone2,phone3).

## Варианты

- Нельзя выбрать один индекс
- Выбрать два набора и найти объединение (5.x++)
- Объединение? В 4.1 есть UNION!

# UNION вместо OR

-     SELECT id FROM objs WHERE shape = 'square' OR color = 'red';

-           SELECT id FROM objs WHERE shape = 'square'
      UNION SELECT id FROM objs WHERE color = 'red';

-               SELECT id FROM objs WHERE shape = 'square'
      UNION ALL SELECT id FROM objs WHERE color = 'red' AND shape != 'square';

- Тесты 4.x: x 1.8 x 1.1

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

- Одинаковая производительность
- Сложнее запрос → сложнее разбить
- Слияние с объединением полность вытеснит UNION

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

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

- Где индекс? A где данные?
- Низкая уникальность
- Быстрее пропустить поиск по индексу

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

- Где индекс? A где данные? Быстрее вовсе не обращаться к данным

-     EXPLAIN SELECT name FROM u WHERE name = 'n' and company = 'g';
      +...+---------------+-------------+...+-------------+...+-----------------+
      |...| possible_keys | key         |...| ref         |...| Extra           |
      +...+---------------+-------------+...+-------------+...+-----------------+
      |...| iname_icomp   | iname_icomp |...| const,const |...| ... Using index |
      +...+---------------+-------------+...+-------------+...+-----------------+

- И даже так:

      EXPLAIN SELECT name FROM u WHERE company = 'g';
      +...+---------------+-------------+...+-------------+...+-----------------+
      |...| possible_keys | key         |...| ref         |...| Extra           |
      +...+---------------+-------------+...+-------------+...+-----------------+
      |...| NULL          | iname_icomp |...| NULL        |...| ... Using index |
      +...+---------------+-------------+...+-------------+...+-----------------+

# Тип операции

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

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

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

- По индексy можно пройти в обоих направлениях
- Можно комбинировать с операциями ref, index, ALL
- Нельзя комбинировать с range
- Нельзя сортировать одновременно в разных направления

# 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                 |
    +...+-------------------+-------------+...+-----------------------------+

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

- USE INDEX(...), FORCE INDEX(...)
- Исправить ошибки оптимизатора

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

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

- Конфликт ORDER BY и WHERE
- INDEX(person, created) не работает
- Либо получить все данные, отсортировать и выбрать 25
- Либо выбирать по порядку, делать проверку и остановиться на 25

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

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

- Вариант по умолчанию, цена сортировки не учитывается
- Чем больше набор тем медленнее запрос

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

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

- USE INDEX()
- Чем меньше набор тем медленнее запрос

-     SELECT t.person FROM t USE INDEX(icreated_iperson)
      WHERE person IN (...) ORDER BY created DESC LIMIT 25

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

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

- Ограничение на количество друзей
- Переключение между запросами (N > F(L, ...))
- Введение доп. условий в запрос (created > -G(N,...))
- Патчинг (спонсирование :) MySQL

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

- Нашли в первой, заглянули во вторую и по цепочке
- Новый параметр оптимизации: порядок
- Один индекс на объединие
- Количество данных возрастает

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

- const(system) выполяются до
- Зависимые объединения (LEFT, RIGHT)
- "Все" сочетания независимых
- Определяет возможные индексы

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

- Порядок фиксированный
- В оптимизации участвуют только условия объединения
- Могут вырождаться в NORMAL (5.0++, 4.x with bugs)
- not exist

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

- В первой таблице участвуют только константы
- Полученные данные можно использовать в объединениях
- Условия определяют сочетания

# Пример

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

- Выбрать активных и проверить принадлежность

      CREATE INDEX u1 ON u(active);
      CREATE UNIQUE INDEX gm1 ON gm(user_id, group_id);

- Выбрать принадлежащих и проверить активность

      CREATE UNIQUE INDEX gm2 ON gm(group_id, user_id);

- Что быстрее?
- А если поискать неактивных в самой большой группе

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

- Многие ко многим - gm(user_id, group_id)
- Поиск без обращения к данным
- Индекс в обоих направления

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

##

    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

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

- DROP ALL INDEXES
- Думайте как mysql
- SELECT COUNT(*) FROM ...
- Не понимаете почему так? STRAIGHT_JOIN, USE INDEX

#

## Вопросы?