Не ме разбирайте погрешно; Обичам филтрирани индекси. Те създават възможности за много по-ефективно използване на I/O и накрая ни позволяват да приложим подходящи ANSI-съвместими уникални ограничения (където е позволено повече от едно NULL). Те обаче далеч не са съвършени. Исках да посоча няколко области, в които филтрираните индекси могат да бъдат подобрени и да ги направят много по-полезни и практични за голяма част от работните натоварвания.
Първо, добрите новини
Филтрираните индекси могат да направят много бърза работа на по-рано скъпи заявки, като използват по-малко място (и следователно намаляват I/O, дори когато са сканирани).
Бърз пример за използване на Sales.SalesOrderDetailEnlarged
(построен с помощта на този скрипт от Джонатан Кехайяс (@SQLPoolBoy)). Тази таблица има 4,8 мм реда, с 587 MB данни и 363 MB индекси. Има само една колона с нула, CarrierTrackingNumber
, така че нека играем с този. В момента таблицата има около половината от тези стойности (2,4 мм) като NULL. Ще намаля това до около 240K, за да симулирам сценарий, при който малък процент от редовете в таблицата действително отговарят на условията за индекс, за да подчертая най-добре предимствата на филтриран индекс. Следната заявка засяга 2,17 мм реда, оставяйки 241 507 реда със стойност NULL за CarrierTrackingNumber
:
UPDATE Sales.SalesOrderDetailEnlarged SET CarrierTrackingNumber = 'x' WHERE CarrierTrackingNumber IS NULL AND SalesOrderID % 10 <> 3;
Сега, да кажем, че има бизнес изискване, при което постоянно искаме да преглеждаме поръчки, които имат продукти, на които все още не е присвоен номер за проследяване (помислете за поръчки, които са разделени и изпратени отделно). В текущата таблица ще изпълним тези заявки (и добавих DBCC командите, за да гарантирам студен кеш във всеки случай):
DBCC DROPCLEANBUFFERS; DBCC FREEPROCCACHE; SELECT COUNT(*) FROM Sales.SalesOrderDetailEnlarged WHERE CarrierTrackingNumber IS NULL; SELECT ProductID, SalesOrderID FROM Sales.SalesOrderDetailEnlarged WHERE CarrierTrackingNumber IS NULL;
Които изискват сканиране на клъстерни индекси и дават следните показатели по време на изпълнение (както са заснети с SQL Sentry Plan Explorer):
В „старите“ дни (което означава след SQL Server 2005) щяхме да създадем този индекс (и всъщност, дори в SQL Server 2012, това е индексът, който SQL Server препоръчва):
CREATE INDEX IX_NotVeryHelpful ON [Sales].[SalesOrderDetailEnlarged] ([CarrierTrackingNumber]) INCLUDE ([SalesOrderID],[ProductID]);
С този индекс на място и стартиране на горните заявки отново, ето показателите, като и двете заявки използват търсене на индекс, както може да очаквате:
И след това махнете този индекс и създадете малко по-различен, като просто добавите WHERE
клауза:
CREATE INDEX IX_Filtered_CTNisNULL ON [Sales].[SalesOrderDetailEnlarged] ([CarrierTrackingNumber]) INCLUDE ([SalesOrderID],[ProductID]) WHERE CarrierTrackingNumber IS NULL;
Получаваме тези резултати и двете заявки използват филтрирания индекс за своите търсения:
Ето допълнителното пространство, изисквано от всеки индекс, в сравнение с намаляването на времето за изпълнение и I/O на горните заявки:
Индекс | Индексно пространство | Добавено пространство | Продължителност | Четене |
---|---|---|---|---|
Няма специален индекс | 363 MB | 15 700 мс | ~164 000 | |
Нефилтриран индекс | 530 MB | 167 MB (+46%) | 169 мс | 1084 |
Филтриран индекс | 367 MB | 4 MB (+1%) | 170 мс | 1084 |
Така че, както можете да видите, филтрираният индекс осигурява подобрения в производителността, които са почти идентични с нефилтрирания индекс (тъй като и двата са в състояние да получат данните си, използвайки същия брой четения), но при много по-ниско съхранение цена, тъй като филтрираният индекс трябва да съхранява и поддържа само редовете, които съответстват на предиката на филтъра.
Сега нека върнем таблицата в първоначалното й състояние:
UPDATE Sales.SalesOrderDetailEnlarged SET CarrierTrackingNumber = NULL WHERE CarrierTrackingNumber = 'x'; DROP INDEX IX_NotVeryHelpful ON Sales.SalesOrderDetailEnlarged; DROP INDEX IX_Filtered_CTNisNULL ON Sales.SalesOrderDetailEnlarged;
Тим Чапман (@chapmandew) и Мишел Уфорд (@sqlfool) са свършили фантастична работа, очертавайки ползите от производителността на филтрираните индекси по свои собствени начини и трябва да разгледате и техните публикации:
- Мишел Уфорд:Филтрирани индекси:Какво трябва да знаете
- Тим Чапман:Радостта на филтрираните индекси
Също така, ANSI-съвместими уникални ограничения (нещо като)
Реших също така накратко да спомена уникалните ограничения, съвместими с ANSI. В SQL Server 2005 бихме създали уникално ограничение като това:
CREATE TABLE dbo.Personnel ( EmployeeID INT PRIMARY KEY, SSN CHAR(9) NULL, -- ... other columns ... CONSTRAINT UQ_SSN UNIQUE(SSN) );
(Можем също така да създадем уникален неклъстериран индекс вместо ограничение; основната реализация е по същество същата.)
Това не е проблем, ако SSN са известни към момента на влизане:
INSERT dbo.Personnel(EmployeeID, SSN) VALUES(1,'111111111'),(2,'111111112');
Също така е добре, ако имаме от време на време SSN, който не е известен към момента на влизане (помислете за кандидат за виза или може би дори чужд работник, който няма SSN и никога няма да има):
INSERT dbo.Personnel(EmployeeID, SSN) VALUES(3,NULL);
Дотук добре. Но какво се случва, когато имаме втора служител с неизвестен SSN?
INSERT dbo.Personnel(EmployeeID, SSN) VALUES(4,NULL);
Резултат:
Msg 2627, ниво 14, състояние 1, ред 1Нарушение на ограничението UNIQUE KEY 'UQ_SSN'. Не може да се вмъкне дублиран ключ в обект 'dbo.Personnel'. Стойността на дублирания ключ е (
Изразът е прекратен.
Така че по всяко време в тази колона може да съществува само една стойност NULL. За разлика от повечето сценарии, това е един случай, в който SQL Server третира две стойности NULL като равни (вместо да определя, че равенството е просто неизвестно и от своя страна невярно). Хората се оплакват от тази непоследователност от години.
Ако това е изискване, сега можем да заобиколим това с помощта на филтрирани индекси:
ALTER TABLE dbo.Personnel DROP CONSTRAINT UQ_SSN; GO CREATE UNIQUE INDEX UQ_SSN ON dbo.Personnel(SSN) WHERE SSN IS NOT NULL;
Сега нашето 4-то вмъкване работи добре, тъй като уникалността се прилага само върху стойностите, които не са NULL. Това е вид измама, но отговаря на основните изисквания, предназначени от стандарта ANSI (въпреки че SQL Server не ни позволява да използваме ALTER TABLE ... ADD CONSTRAINT
синтаксис за създаване на филтрирано уникално ограничение).
Но, задръжте телефона
Това са страхотни примери за това какво можем да правим с филтрирани индекси, но има много неща, които все още не можем да направим, както и няколко ограничения и проблеми, които възникват в резултат на това.
Актуализации на статистиката
Това е едно от по-важните ограничения IMHO. Филтрираните индекси не се възползват от автоматично актуализиране на статистически данни въз основа на процентна промяна на подмножеството на таблицата, което е идентифицирано от предиката на филтъра; той се основава (както всички нефилтрирани индекси) на оттичане спрямо цялата таблица. Това означава, че в зависимост от това какъв процент от таблицата е във филтрирания индекс, броят на редовете в индекса може да се увеличи четири пъти или наполовина и статистиката няма да се актуализира, освен ако не го направите ръчно. Кимбърли Трип даде страхотна информация за това (и Гейл Шоу цитира пример, където са били необходими 257 000 актуализации, преди да бъдат актуализирани статистиките за филтриран индекс, който съдържа само 10 000 реда):
http://www.sqlskills.com/blogs/kimberly/filtered-indexes-and-filtered-stats-might-become-seriously-out-of-date/
http://www.sqlskills.com/ blogs/kimberly/category/filtered-indexes/
Също така, колегата на Кимбърли, Джо Сак (@JosephSack), е подал елемент Connect, който предлага коригиране на това поведение както за филтрирани индекси, така и за филтрирана статистика.
Ограничения на израза на филтъра
Има няколко конструкции, които не можете да използвате във филтър предикат, като NOT IN
, OR
и динамични / недетерминирани предикати като WHERE col >= DATEADD(DAY, -1, GETDATE())
. Освен това оптимизаторът може да не разпознае филтриран индекс, ако предикатът не съвпада точно с WHERE
клауза в дефиницията на индекса. Ето няколко елемента на Connect, които се опитват да привлекат малко подкрепа за по-добро покритие тук:
Филтриран индекс не позволява филтри на дизюнкции | (затворено:по проект) |
Създаването на филтриран индекс не бе успешно с клауза NOT IN | (затворено:по проект) |
Поддръжка за по-сложна клауза WHERE във филтрирани индекси | (активен) |
Други потенциални употреби в момента не са възможни
Понастоящем не можем да създадем филтриран индекс за постоянна изчислена колона, дори ако е детерминирана. Не можем да посочим външен ключ към уникален филтриран индекс; ако искаме индекс да поддържа външния ключ в допълнение към заявките, поддържани от филтрирания индекс, трябва да създадем втори, излишен, нефилтриран индекс. И ето няколко други подобни ограничения, които или са били пренебрегнати, или все още не са взети предвид:
Трябва да е възможно да се създаде филтриран индекс върху детерминирана персистирана изчислена колона | (активен) |
Разрешаване на филтриран уникален индекс да бъде кандидат ключ за външен ключ | (активен) |
възможност за създаване на филтърни индекси на индексирани изгледи | (затворено:няма да се поправи) |
Грешка при разделяне 1908 – Подобрете разделянето | (затворено:няма да се поправи) |
СЪЗДАВАНЕ НА ИНДЕКС НА КОЛОНИ ЗА КОЛОНИ | (активен) |
Проблеми с MERGE
И MERGE
се появява още едно в моя списък „внимавайте“:
MERGE оценява филтриран индекс на ред, а не след операция, което причинява нарушение на филтриран индекс | (затворено:няма да се поправи) |
MERGE не успява да се актуализира с филтриран индекс на място | (затворено:фиксирано) |
Грешка в израза MERGE, когато INSERT/DELETE се използва и филтрира индекс | (активен) |
СЛИВАНЕ неправилно докладва за уникални нарушения на ключовете | (активен) |
Докато една от тези (привидно тясно свързани) грешки казва, че е коригирана в SQL Server 2012, може да се наложи да се свържете с PSS, ако се сблъскате с някаква вариация на този проблем, особено в по-ранни версии (или спрете да използвате MERGE , както вече предлагах).
Инструмент / DMV / вградени ограничения
Има много DMV, DBCC команди, системни процедури и клиентски инструменти, на които започваме да разчитаме с течение на времето. Въпреки това, не всички тези неща се актуализират, за да се възползват от новите функции; филтрираните индекси не са изключение. Следните елементи на Connect посочват някои проблеми, които могат да ви спънат, ако очаквате да работят с филтрирани индекси:
Няма начин за създаване на филтриран индекс от SSMS, докато проектирате нова таблица | (затворено:няма да се поправи) |
Филтърният израз на филтриран индекс се губи, когато таблица се модифицира от дизайнера на таблици | (затворено:няма да се поправи) |
Дизайнерът на таблици не скриптира клауза WHERE във филтрирани индекси | (активен) |
Конструкторът на SSMS таблици не запазва израза на индексния филтър при повторното изграждане на таблица | (затворено:няма да се поправи) |
DBCC PAGE неправилен изход с филтрирани индекси | (активен) |
SQL 2008 Филтрирани предложения за индекси от DM Views и DTA | (затворено:няма да се поправи) |
Подобрения на липсващите индекси DMV за филтрирани индекси | (затворено:няма да се поправи) |
Синтактична грешка при репликиране на компресирани филтрирани индекси | (затворено:няма да се поправи) |
Агент:заданията използват опции, които не са по подразбиране, когато изпълняват T-SQL скрипт | (затворено:няма да се поправи) |
Преглед на зависимостите е неуспешен с Transact-SQL грешка 515 | (активен) |
Зависимостите на изгледа не успяват на определени обекти | (затворено:няма да се поправи) |
Разликите в опциите на индекса не са открити в схемата за сравнение за две бази данни | (затворен:външен) |
Предложете излагане на условие за индексен филтър във всички изгледи на информация за индекса | (затворено:няма да се поправи) |
Резултатите от sp_helpIndex трябва да включват филтърния израз на филтърните индекси | (активен) |
Претоварване на sp_help, sp_columns, sp_helpindex за функции от 2008 г. | (затворено:няма да се поправи) |
За последните три не задържайте дъха си – малко вероятно е Microsoft да инвестира време в sp_ процедури, DMV, INFORMATION_SCHEMA изгледи и т.н. Вместо това вижте пренаписванията на sp_helpindex на Kimberly Tripp, които включват информация за филтрирани индекси заедно с други нови функции, които Microsoft изостави.
Ограничения на оптимизатора
Има няколко елемента на Connect, които описват случаите, когато филтрираните индекси *могат* да бъдат използвани от оптимизатора, но вместо това се игнорират. В някои случаи те не се считат за „бъгове“, а по-скоро за „пропуски във функционалността“…
SQL не използва филтриран индекс за проста заявка | (затворено:по проект) |
Планът за изпълнение на филтриран индекс не е оптимизиран | (затворено:няма да се поправи) |
Филтриран индекс не се използва и ключово търсене без изход | (затворено:няма да се поправи) |
Използването на филтриран индекс в BIT колона зависи от точния SQL израз, използван в клаузата WHERE | (активен) |
Заявката към свързан сървър не се оптимизира правилно, когато съществува филтриран уникален индекс | (затворено:няма да се поправи) |
Row_Number() дава непредвидими резултати върху свързани сървъри, където се използват филтрирани индекси | (затворено:без повторение) |
Очевидно филтриран индекс не се използва от QP | (затворено:по проект) |
Разпознаване на уникални филтрирани индекси като уникални | (активен) |
Пол Уайт (@SQL_Kiwi) наскоро публикува тук на SQLPerformance.com публикация, която се впуска в много подробности относно няколко от горните ограничения на оптимизатора.
И Тим Чапман написа страхотна публикация, в която очертава някои други ограничения на филтрираните индекси – като например невъзможността за съпоставяне на предиката с локална променлива (фиксирана в 2008 R2 SP1) и невъзможността да се посочи филтриран индекс в намек за индекс.
Заключение
Филтрираните индекси имат голям потенциал и имах изключително големи надежди за тях, когато бяха представени за първи път в SQL Server 2008. Въпреки това повечето от ограниченията, които се доставят с първата им версия, все още съществуват днес, един и половина (или две, в зависимост от перспектива) големи издания по-късно. Горното изглежда като доста обширен списък с елементи, които трябва да бъдат разгледани, но не исках да се окаже по този начин. Просто искам хората да са наясно с огромния брой потенциални проблеми, които може да се наложи да обмислят, когато се възползват от филтрираните индекси.