Database
 sql >> база данни >  >> RDS >> Database

Митове за производителността:клъстерни срещу не-клъстерни индекси

Наскоро ме смъмриха, че предположих, че в някои случаи неклъстерираният индекс ще се представи по-добре за конкретна заявка от клъстерирания индекс. Този човек заяви, че клъстерираният индекс винаги е най-добрият, защото винаги покрива по дефиниция и че всеки неклъстериран индекс с някои или всички едни и същи ключови колони винаги е бил излишен.

С радост ще се съглася, че клъстерираният индекс винаги покрива (и за да избегнем неясноти тук, ще се придържаме към дискови таблици с традиционни индекси на B-дърво).

Не съм съгласен обаче, че групираният индекс е винаги по-бързо от неклъстериран индекс. Също така не съм съгласен, че винаги е излишно да се създава неклъстериран индекс или уникално ограничение, състоящо се от същите (или някои от същите) колони в ключа за клъстериране.

Да вземем този пример, Warehouse.StockItemTransactions , от WideWorldImporters. Клъстерираният индекс се реализира чрез първичен ключ само на StockItemTransactionID колона (доста типично, когато имате някакъв вид заместващ идентификатор, генериран от IDENTITY или SEQUENCE).

Доста обичайно е да се изисква преброяване на цялата таблица (въпреки че в много случаи има по-добри начини). Това може да бъде за случайна проверка или като част от процедура за пагинация. Повечето хора ще го направят по следния начин:

SELECT COUNT(*) 
  FROM Warehouse.StockItemTransactions;

С текущата схема това ще използва неклъстериран индекс:

Знаем, че неклъстерираният индекс не съдържа всички колони в клъстерирания индекс. Операцията за броене трябва само да е сигурна, че всички редове са включени, без да се интересува кои колони присъстват, така че SQL Server обикновено избира индекса с най-малък брой страници (в този случай избраният индекс има ~414 страници).

Сега нека стартираме заявката отново, като този път я сравняваме с намекната заявка, която принуждава използването на клъстерирания индекс.

SELECT COUNT(*) 
  FROM Warehouse.StockItemTransactions;
 
SELECT COUNT(*) /* with hint */
  FROM Warehouse.StockItemTransactions WITH (INDEX(1));

Получаваме почти идентична форма на план, но можем да видим огромна разлика в показанията (414 за избрания индекс срещу 1982 за клъстерирания индекс):

Продължителността е малко по-висока за клъстерирания индекс, но разликата е незначителна, когато имаме работа с малко количество кеширани данни на бърз диск. Това несъответствие би било много по-изразено при повече данни, на бавен диск или на система с напрежение в паметта.

Ако погледнем подсказките за операциите за сканиране, можем да видим, че докато броят на редовете и приблизителните разходи на процесора са идентични, голямата разлика идва от приблизителните I/O разходи (тъй като SQL Server знае, че има повече страници в клъстериран индекс от неклъстерирания индекс):

Можем да видим тази разлика още по-ясно, ако създадем нов, уникален индекс само в колоната ID (което го прави „излишен“ с клъстерирания индекс, нали?):

CREATE UNIQUE INDEX NewUniqueNCIIndex 
  ON Warehouse.StockItemTransactions(StockItemTransactionID);

Изпълнението на подобна заявка с изричен намек за индекс произвежда същата форма на плана, но дори по-ниска прогнозирана I/O цена (и дори по-ниска продължителност) – вижте изображението вдясно. И ако изпълните оригиналната заявка без намек, ще видите, че SQL Server вече избира и този индекс.

Може да изглежда очевидно, но много хора биха повярвали, че клъстерираният индекс е най-добрият избор тук. SQL Server почти винаги ще предпочита метода, който ще осигури най-евтиния начин за извършване на цялото I/O, а в случай на пълно сканиране, това ще бъде най-слабият индекс. Това може да се случи и при двата типа търсения (единично и диапазонно сканиране), поне когато индексът покрива.

Сега, както винаги, това не по някакъв начин означава, че трябва да отидете и да създадете допълнителни индекси за всичките си таблици, за да удовлетворите заявките за броене. Това не само е неефективен начин за проверка на размера на таблицата (отново вижте тази статия), но и индекс за поддръжка, който би трябвало да означава, че изпълнявате тази заявка по-често, отколкото актуализирате данните. Не забравяйте, че всеки индекс изисква място на диска, място в паметта и всички записи в таблицата трябва също да докосват всеки индекс (филтрираните индекси настрана).

Резюме

Бих могъл да измисля много други примери, които показват кога неклъстериран може да бъде полезен и да си струва разходите за поддръжка, дори когато се дублират ключовите колони на клъстерирания индекс. Неклъстерираните индекси могат да бъдат създадени със същите ключови колони, но в различен ключов ред, или с различен ASC/DESC на самите колони, за да поддържат по-добре алтернативен ред на представяне. Можете също да имате неклъстерирани индекси, които носят само малка подмножество от редове чрез използването на филтър. И накрая, ако можете да удовлетворите най-често срещаните си заявки с по-слаби, неклъстерирани индекси, това е по-добре и за консумация на памет.

Но всъщност моята цел на тази поредица е просто да покажа контрапример, който илюстрира глупостта да се правят общи изявления като това. Ще ви оставя с обяснение от Пол Уайт, който в отговор на DBA.SE обяснява защо такъв неклъстериран индекс всъщност може да работи много по-добре от клъстериран индекс. Това е вярно дори когато и двете използват всеки тип търсене:

  • Разлика между клъстерирано търсене на индекс и неклъстерирано търсене на индекс

  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Как да получите текущата дата и час (без часова зона) в T-SQL

  2. Прагове за оптимизиране – групиране и агрегиране на данни, част 5

  3. Свързване на MS SQL към IRI Workbench

  4. Какво представлява Greenplum Database? Въведение в базата данни Big Data

  5. T-SQL вторник #65:Научете нещо ново