Когда мне следует использовать составной индекс?


133
  1. Когда мне следует использовать составной индекс в базе данных?
  2. Каковы разветвления производительности при использовании составного индекса)?
  3. Почему я должен использовать составной индекс?

Например, у меня есть homesтаблица:

CREATE TABLE IF NOT EXISTS `homes` (
  `home_id` int(10) unsigned NOT NULL auto_increment,
  `sqft` smallint(5) unsigned NOT NULL,
  `year_built` smallint(5) unsigned NOT NULL,
  `geolat` decimal(10,6) default NULL,
  `geolng` decimal(10,6) default NULL,
  PRIMARY KEY  (`home_id`),
  KEY `geolat` (`geolat`),
  KEY `geolng` (`geolng`),
) ENGINE=InnoDB  ;

Имеет ли смысл использовать составной индекс для обоих geolatи geolng, чтобы:

Заменяю:

  KEY `geolat` (`geolat`),
  KEY `geolng` (`geolng`),

с участием:

KEY `geolat_geolng` (`geolat`, `geolng`)

Если так:

  • Зачем?
  • Каковы последствия использования составного индекса для повышения производительности)?

ОБНОВИТЬ:

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

SELECT * FROM homes
WHERE geolat BETWEEN ??? AND ???
AND geolng BETWEEN ??? AND ???

ОБНОВЛЕНИЕ 2:

Со следующей схемой базы данных:

CREATE TABLE IF NOT EXISTS `homes` (
  `home_id` int(10) unsigned NOT NULL auto_increment,
  `primary_photo_group_id` int(10) unsigned NOT NULL default '0',
  `customer_id` bigint(20) unsigned NOT NULL,
  `account_type_id` int(11) NOT NULL,
  `address` varchar(128) collate utf8_unicode_ci NOT NULL,
  `city` varchar(64) collate utf8_unicode_ci NOT NULL,
  `state` varchar(2) collate utf8_unicode_ci NOT NULL,
  `zip` mediumint(8) unsigned NOT NULL,
  `price` mediumint(8) unsigned NOT NULL,
  `sqft` smallint(5) unsigned NOT NULL,
  `year_built` smallint(5) unsigned NOT NULL,
  `num_of_beds` tinyint(3) unsigned NOT NULL,
  `num_of_baths` decimal(3,1) unsigned NOT NULL,
  `num_of_floors` tinyint(3) unsigned NOT NULL,
  `description` text collate utf8_unicode_ci,
  `geolat` decimal(10,6) default NULL,
  `geolng` decimal(10,6) default NULL,
  `display_status` tinyint(1) NOT NULL,
  `date_listed` timestamp NOT NULL default CURRENT_TIMESTAMP,
  `contact_email` varchar(100) collate utf8_unicode_ci NOT NULL,
  `contact_phone_number` varchar(15) collate utf8_unicode_ci NOT NULL,
  PRIMARY KEY  (`home_id`),
  KEY `customer_id` (`customer_id`),
  KEY `city` (`city`),
  KEY `num_of_beds` (`num_of_beds`),
  KEY `num_of_baths` (`num_of_baths`),
  KEY `geolat` (`geolat`),
  KEY `geolng` (`geolng`),
  KEY `account_type_id` (`account_type_id`),
  KEY `display_status` (`display_status`),
  KEY `sqft` (`sqft`),
  KEY `price` (`price`),
  KEY `primary_photo_group_id` (`primary_photo_group_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=8 ;

Используя следующий SQL:

EXPLAIN SELECT  homes.home_id,
                    address,
                    city,
                    state,
                    zip,
                    price,
                    sqft,
                    year_built,
                    account_type_id,
                    num_of_beds,
                    num_of_baths,
                    geolat,
                    geolng,
                    photo_id,
                    photo_url_dir
            FROM homes
            LEFT OUTER JOIN home_photos ON homes.home_id = home_photos.home_id
                AND homes.primary_photo_group_id = home_photos.home_photo_group_id
                AND home_photos.home_photo_type_id = 2
            WHERE homes.display_status = true
            AND homes.geolat BETWEEN -100 AND 100
            AND homes.geolng BETWEEN -100 AND 100

EXPLAIN возвращает:

id  select_type  table        type  possible_keys                                    key                  key_len  ref     rows  Extra
----------------------------------------------------------------------------------------------------------
1   SIMPLE       homes        ref   geolat,geolng,display_status                     display_status       1        const   2     Using where
1  SIMPLE        home_photos  ref   home_id,home_photo_type_id,home_photo_group_id   home_photo_group_id  4        homes.primary_photo_group_id   4  

Я не совсем понимаю, как читать команду EXPLAIN. Это хорошо или плохо. В настоящий момент я НЕ использую составной индекс для геолокации и геолокации. Должна ли я быть?

Ответы:


111

Вы должны использовать составной индекс, когда используете запросы, которые от него выигрывают. Составной индекс, который выглядит так:

index( column_A, column_B, column_C )

принесет пользу запросу, в котором эти поля используются для объединения, фильтрации и иногда выбора. Это также принесет пользу запросам, которые используют самые левые подмножества столбцов в этой композиции. Таким образом, указанный выше индекс также удовлетворит запросы, которым требуется

index( column_A, column_B, column_C )
index( column_A, column_B )
index( column_A )

Но это не поможет (по крайней мере, не напрямую, возможно, частично поможет, если нет лучших индексов) для запросов, которые нуждаются в

index( column_A, column_C )

Обратите внимание, как отсутствует column_B.

В вашем исходном примере составной индекс для двух измерений в основном будет полезен для запросов, которые запрашивают оба измерения или самое левое измерение, но не самое правое измерение. Если вы всегда запрашиваете два измерения, лучше всего использовать составной индекс, неважно, какое из них будет первым (скорее всего).


1
Марк, я обновил свой исходный пост (обновление 2). Это мой настоящий вопрос. Моя фактическая схема БД. И что возвращает команда EXPLAIN. Итак, с этой информацией - следует ли мне использовать составной индекс. Мне все еще неясно. Заранее спасибо.
Teddy

Отметьте, соответствует ли составной индекс в вашем ответе index (column_C)?
Борис Д. Теохаров

Я не уверен, что понимаю ваш вопрос. Но если вы спрашиваете, поможет ли индекс (A, B, C) запрос, фильтрующий по столбцу C, ответ обычно будет отрицательным, он не будет использовать индекс для фильтрации. Однако он может использовать индекс для исключения сканирования таблицы, если вы выбираете только подмножество ABC. Итак, это другое, но взаимосвязанное. Но для типичного использования индексов для включения фильтрации ответ отрицательный.
Марк Канлас

1
-1, потому что составной индекс не помогает WHERE geolat BETWEEN ??? AND ??? AND geolng BETWEEN ??? AND ???. Он остановится после первого поля. Ответ из «Вопроса переполнения» объясняет, почему.
Рик Джеймс,

1
@felwithe MySQL может использовать только один индекс для каждой таблицы в запросе (есть исключения, например, слияние индексов). В идеале это означает, что таблица в запросе должна использовать единый индекс для всех предложений where, соединения таблиц, группировки по и порядка по. Таким образом, отдельный индекс для каждого столбца может работать не всегда, но составной индекс может творить чудеса.
АХИЛ МЭТЬЮ

57

Представьте, что у вас есть три следующих вопроса:

Запрос I:

SELECT * FROM homes WHERE `geolat`=42.9 AND `geolng`=36.4

Запрос II:

SELECT * FROM homes WHERE `geolat`=42.9

Запрос III:

SELECT * FROM homes WHERE `geolng`=36.4

Если у вас есть отдельный индекс для каждого столбца, все три запроса используют индексы. В MySQL, если у вас есть составной индекс ( geolat,geolng ), только запрос I и запрос II (который использует первую часть составного индекса) используют индексы. В этом случае запрос III требует поиска по всей таблице.

В индексах с несколькими столбцами разделе руководства « несколькими столбцами» четко объясняется, как работают индексы с несколькими столбцами, поэтому я не хочу повторно набирать руководство.

На странице справочного руководства MySQL :

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

Если вы используете разделенный индекс для столбцов geolat и geolng, у вас есть два разных индекса в вашей таблице, которые вы можете искать независимо.

INDEX geolat
-----------
VALUE RRN
36.4  1
36.4  8
36.6  2
37.8  3
37.8  12
41.4  4

INDEX geolng
-----------
VALUE RRN
26.1  1
26.1  8
29.6  2
29.6  3
30.1  12
34.7  4

Если вы используете составной индекс, у вас есть только один индекс для обоих столбцов:

INDEX (geolat, geolng)
-----------
VALUE      RRN
36.4,26.1  1
36.4,26.1  8
36.6,29.6  2
37.8,29.6  3
37.8,30.1  12
41.4,34.7  4

RRN - относительный номер записи (для упрощения можно сказать ID). Первые два индекса генерируются отдельно, а третий - составной. Как вы можете видеть, вы можете выполнять поиск на основе геолокации на составном, поскольку он проиндексирован геолатом, однако можно искать по геолату или "геолату И геолжингу" (поскольку геоланг является индексом второго уровня).

Также ознакомьтесь с разделом руководства, как MySQL использует индексы .


1
На самом деле у меня нет ни одного из этих вопросов. Мой запрос указан в исходном сообщении. Моя просьба - вернуть дома в квадратной сетке. Я знаю о пространственном и не пытаюсь вычислять расстояния. Я просто хочу знать, имеет ли смысл использование составного индекса, когда я пытаюсь отобразить все дома в определенной географической сетке (например, район / город / округ)
Тедди,

Eyazici, обновил свой исходный пост (обновление 2). Это мой настоящий вопрос. Моя фактическая схема БД. И что возвращает команда EXPLAIN. Итак, с этой информацией - следует ли мне использовать составной индекс. Мне все еще неясно. Заранее спасибо
Тедди

@ "На самом деле, у меня нет ни одного из этих вопросов.". На самом деле у вас есть, я использовал простое условие WHERE для объяснения базовой логики. При использовании условия (например, WHERE) в столбце MySQL пытается использовать индексы, когда это возможно. «x BETWEEN a AND b» аналогично «x> a AND x <b». В условном запросе вы использовали столбцы geolng и geolat. Если вы используете составной индекс "(geolat, geolng)" ваш "AND geolng BETWEEN ??? AND ???" условный не дает преимуществ индекса (это для MySQL). Поэтому для вашего сценария следует использовать отдельный индекс для каждого столбца.
Emre Yazici

Я не понимаю Почему я должен использовать отдельные индексы для геолокации и геолокации, если я ВСЕГДА выполняю запрос, включающий оба столбца
Тедди,

1
Нет. Когда встречается "диапазон" (как и BETWEEN), никакие другие поля индекса не рассматриваются! Так что сводный индекс ничем не лучше.
Рик Джеймс

19

Может быть неправильное представление о том, что делает составной индекс. Многие думают, что составной индекс можно использовать для оптимизации поискового запроса, если whereпредложение охватывает индексированные столбцы, в вашем случае geolatи geolng. Давайте копнем глубже:

Я считаю, что ваши данные о координатах домов будут случайными десятичными знаками как таковые:

home_id  geolat  geolng
   1    20.1243  50.4521
   2    22.6456  51.1564
   3    13.5464  45.4562
   4    55.5642 166.5756
   5    24.2624  27.4564
   6    62.1564  24.2542
...

Поскольку geolatи geolngценности почти не повторяются. Составной индекс на geolatи geolngбудет выглядеть примерно так:

index_id  geolat  geolng
   1     20.1243  50.4521
   2     20.1244  61.1564
   3     20.1251  55.4562
   4     20.1293  66.5756
   5     20.1302  57.4564
   6     20.1311  54.2542
...

Поэтому второй столбец сводного индекса в принципе бесполезен ! Скорость вашего запроса с составным индексом, вероятно, будет аналогична скорости индекса только для geolatстолбца.

Как упоминал Уилл, MySQL обеспечивает поддержку пространственного расширения . Пространственная точка хранится в одном столбце вместо двух отдельных lat lngстолбцов. К такому столбцу можно применить пространственный индекс. Однако, исходя из моего личного опыта, эффективность можно переоценить. Возможно, пространственный индекс не решает двумерную проблему, а просто ускоряет поиск с использованием R-деревьев с квадратичным разбиением .

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


5

Составные индексы очень эффективны, поскольку они:

  • Обеспечить целостность конструкции
  • Включить сортировку по ФИЛЬТРЕННОМУ идентификатору

ОБЕСПЕЧЕНИЕ ЦЕЛОСТНОСТИ СТРУКТУРЫ

Составные индексы - это не просто еще один тип индекса; они могут предоставить НЕОБХОДИмую структуру для таблицы, обеспечивая целостность в качестве первичного ключа.

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

Для создания таблиц друзей (например, для социальной сети) нам понадобятся 2 столбца: user_id, friend_id .

Структура стола

user_id (medium_int)
friend_id (medium_int)

Primary Key -> (user_id, friend_id)

По сути, первичный ключ (PK) уникален, и, создав составной PK, Innodb автоматически проверяет отсутствие дубликатов user_id, friend_idпри добавлении новой записи. Это ожидаемое поведение, поскольку ни у одного пользователя не должно быть более 1 записи (связи отношения) сfriend_id = 2 .

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

user_friend_id
user_id
friend_id

Primary Key -> (user_friend_id)

Теперь, когда добавляется новая запись, нам нужно будет проверить, что предыдущая запись с комбинацией user_id, friend_idеще не существует.

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

ВКЛЮЧИТЬ СОРТИРОВКУ ПО ФИЛЬТРОВАННОМУ ИДЕНТИФИКАТОРУ

Очень часто набор записей сортируется по времени публикации (метка времени или дата-время). Обычно это означает публикацию по заданному идентификатору. Вот пример

Таблица User_Wall_Posts (подумайте, если посты на стене Facebook)

user_id (medium_int)
timestamp (timestamp)
author_id (medium_int)
comment_post (text)

Primary Key -> (user_id, timestamp, author_id)

Мы хотим запросить и найти все сообщения для user_id = 10комментариев и отсортировать их по timestamp(дате).

SQL ЗАПРОС

SELECT * FROM User_Wall_Posts WHERE user_id = 10 ORDER BY timestamp DES

Составной PK позволяет Mysql фильтровать и сортировать результаты с помощью индекса; Mysql не нужно использовать временный файл или файловую сортировку для получения результатов. Без составного ключа это было бы невозможно и привело бы к очень неэффективному запросу.

Таким образом, составные ключи очень эффективны и подходят больше, чем простая проблема: «Я хочу искать, column_a, column_bпоэтому я буду использовать составные ключи. Для моей текущей схемы базы данных у меня столько же составных ключей, сколько и одиночных ключей. Не упускайте из виду использование составного ключа!


5

Составные индексы полезны для

  • 0 или более предложений "=" плюс
  • не более одного предложения диапазона.

Составной индекс не может обрабатывать два диапазона. Я обсуждаю это далее в своей индексной кулинарной книге .

Найти ближайший - Если вопрос действительно в оптимизации

WHERE geolat BETWEEN ??? AND ???
  AND geolng BETWEEN ??? AND ???

тогда ни один индекс не сможет обрабатывать оба измерения.

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

Это PARTITIONsполосы диапазонов широты. PRIMARY KEYНамеренно начинается с долготой , так что полезные строки, вероятно, будут в том же блоке. Хранимая процедура организует беспорядочный код для выполнения order by... limit...и увеличения «квадрата» вокруг цели, пока у вас не будет достаточно кофеен (или чего-то еще). Он также заботится о вычислениях по дуге большого круга и обработке линии дат и полюсов.

Больше

Я написал еще один блог; он сравнивает 5 способов выполнения поиска по широте и долготе: http://mysql.rjweb.org/doc.php/latlng#presentation_choices (Ссылка, указанная выше, упоминается как один из 5.) Один из других способов - это, и указывает, что они оптимальны для конкретного случая :

INDEX(geolat, geolng),
INDEX(geolng, geolat)

То есть важно иметь оба столбца в двух индексах и не иметь индексов с одним столбцом для geolat и geolng.


1

Нет никакого черного и белого, универсальный ответ.

Вы должны использовать составной индекс, если он будет полезен вашей рабочей нагрузке запроса.

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

Составной индекс вступает в игру, когда запросы могут быть полностью удовлетворены из этого индекса.

ОБНОВЛЕНИЕ (в ответ на редактирование опубликованного вопроса): если вы выбираете * из таблицы, можно использовать составной индекс, но нельзя. Для уверенности вам нужно будет запустить EXPLAIN PLAN .


Имеет ли смысл использовать составной индекс для данных о геолокации (широта и долгота)?
Teddy

1
Это полностью зависит от того, какие запросы выполняются к этой таблице.
Mitch Wheat

Я обновил свое исходное сообщение, включив в него наиболее часто выполняемый запрос. Смотри выше.
Тедди,

1

Для пространственного поиска вам понадобится R-Tree алгоритм , который позволяет очень быстро искать географические области. Именно то, что вам нужно для этой работы.

Некоторые базы данных имеют встроенные пространственные индексы. Быстрый поиск в Google показывает, что они есть в MySQL 5 (что, глядя на ваш SQL, я предполагаю, что вы используете MySQL).


1

Составной индекс может быть полезен, когда вы хотите оптимизировать group byпредложение (см. Эту статью http://dev.mysql.com/doc/refman/5.0/en/group-by-optimization.html ). Пожалуйста, обратите внимание:

Наиболее важные предварительные условия для использования индексов для GROUP BY заключаются в том, что все столбцы GROUP BY ссылаются на атрибуты из одного и того же индекса и что индекс хранит свои ключи по порядку (например, это индекс BTREE, а не индекс HASH)


GROUP BYне упоминалось.
Рик Джеймс

Где не упоминалось? :) Об этом явно сказано в той статье, о которой я говорил. И он отвечает на заданные вопросы: когда следует использовать составной индекс в базе данных? Каковы разветвления производительности при использовании составного индекса)? Почему я должен использовать составной индекс?
Александр

Исправление: GROUP BYне упоминалось в ОП.
Рик Джеймс

Конечно, это был ответ - один из случаев, когда мы использовали бы составной индекс в базе данных.
Александр

0

Я с @Mitch, полностью зависит от ваших запросов. К счастью, вы можете создавать и удалять индексы в любое время, и вы можете добавить ключевое слово EXPLAIN к своим запросам, чтобы увидеть, использует ли анализатор запросов индексы.

Если вы будете искать точную пару широта / долгота, этот индекс, вероятно, будет иметь смысл. Но вы, вероятно, будете искать дома на определенном расстоянии от определенного места, поэтому ваши запросы будут выглядеть примерно так (см. Источник ):

select *, sqrt(  pow(h2.geolat - h1.geolat,  2) 
               + pow(h2.geolng - h1.geolng, 2) ) as distance
from homes h1, homes h2
where h1.home_id = 12345 and h2.home_id != h1.home_id
order by distance

и, скорее всего, индекс вообще не поможет. Для геопространственных запросов вам понадобится что-то вроде этого .

Обновление: с этим запросом:

SELECT * FROM homes
WHERE geolat BETWEEN ??? AND ???
AND geolng BETWEEN ??? AND ???

Анализатор запросов может использовать только индекс геолокации, или индекс только геолокацию, или, возможно, оба индекса. Я не думаю, что он будет использовать составной индекс. Но легко попробовать каждую из этих перестановок на реальном наборе данных, а затем (а) посмотреть, что вам говорит EXPLAIN, и (б) измерить время, которое действительно занимает запрос.


Я просто использую желание вернуться домой в квадратной сетке. Я знаю пространственное, поэтому не пытаюсь вычислить расстояние. Я просто хочу вернуть дома в квадратную сетку и хочу, чтобы это произошло быстро. Таким образом, я хочу убедиться, что мои индексы настроены правильно. Это помогает?
Teddy
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.