Чи слід індексувати бітове поле в SQL Server?


99

Я пам’ятаю, читаючи в один момент, що індексувати поле з низькою кардинальністю (мала кількість чітких значень) насправді не варто робити. Зізнаюся, я не знаю достатньо про те, як працюють індекси, щоб зрозуміти, чому це так.

Що робити, якщо у мене є таблиця зі 100 мільйонами рядків, і я вибираю записи, де бітове поле дорівнює 1? Скажімо, що в будь-який момент часу існує лише декілька записів, де поле бітів дорівнює 1 (на відміну від 0). Чи варто індексувати це бітове поле чи ні? Чому?

Звичайно, я можу просто перевірити його і перевірити план виконання, і я це зроблю, але мені також цікаво теорія, що стоїть за ним. Коли важливість кардинальності і коли ні?


Це звичайний запит? Це, можливо, варто того, щоб шукати "кілька" записів, але не допоможе вам сильно в інших рядках. Чи є інші способи ідентифікації даних?
жассон сальдо

4
Хоча я не думаю, що я би індексував ПОСЛІДНО бітний стовпець, дуже часто включати бітові стовпці як частину складного індексу. Простим прикладом може бути індекс на ACTIVE, LASTNAME замість просто прізвища, коли ваша програма майже завжди шукає активних клієнтів.
BradC

"Я пам'ятаю, як читав в один момент, що індексувати поле з низькою кардинальністю (мала кількість чітких значень) робити насправді не варто" Це тому, що SQL Server майже завжди вважає його більш ефективним просто виконати сканування таблиці, ніж читати покажчик. Тому в основному ваш індекс ніколи не звикне, і його марно підтримувати. Як говорили інші, у складному індексі це може бути нормально.
діджей.

5
Я б не погодився. Якщо ваш розподіл становить 50/50, то ви ніколи не використовуєте індекс, оскільки це було б просто швидше зробити сканування таблиці. Однак якщо у вас є лише 5, 1 значення та 1 мільйон 0 значень, дуже ймовірно буде використовувати індекс під час пошуку 1.
Kibbee

1
У прикладі, який ви навели, я б більше схильний ставити LastName першим. Це залежить від конкретного навантаження на запит, але загалом, що спочатку має більш вибірковий стовпець, значить, індекс є більш імовірним.
Мітч Пшеничний

Відповіді:


72

Поміркуйте, що таке індекс у SQL - а індекс - це дійсно шматок пам’яті, що вказує на інші шматки пам’яті (тобто вказівники на рядки). Індекс розбивається на сторінки, щоб частини індексу можна було завантажувати та вивантажувати з пам'яті залежно від використання.

Коли ви запитуєте набір рядків, SQL використовує індекс, щоб знайти рядки швидше, ніж сканування таблиці (дивлячись на кожен рядок).

SQL має кластерні та некластеризовані індекси. Я розумію кластерні індекси, що вони групують схожі значення індексу на одній сторінці. Таким чином, коли ви запитуєте всі рядки, що відповідають значенню індексу, SQL може повернути ці рядки з кластерної сторінки пам'яті. Ось чому спроба кластерного індексу колонки GUID є поганою ідеєю - ви не намагаєтеся кластеризувати випадкові значення.

Коли індексується цілий стовпець, індекс SQL містить набір рядків для кожного значення індексу. Якщо у вас діапазон від 1 до 10, то у вас буде 10 покажчиків. Залежно від того, скільки рядків існує, це може бути по-різному. Якщо ваш запит шукає індекс, що відповідає "1", а там, де Ім'я містить "Fred" (припускаючи, що стовпець Ім'я не індексується), SQL дуже швидко отримує набір рядків, що відповідають "1", а потім сканує таблицю, щоб знайти решту.

Отож, що насправді робить SQL - це намагатися зменшити робочий набір (кількість рядків), який він повинен повторити.

Коли ви індексуєте бітове поле (або якийсь вузький діапазон), ви зменшуєте лише робочий набір на кількість рядків, що відповідають цьому значенню. Якщо у вас невелика кількість рядків, це збільшить ваш робочий набір значно. Для великої кількості рядків з розподілом 50/50 це може придбати вам дуже низький приріст продуктивності порівняно з постійним оновленням індексу.

Причина, по якій усі говорять про тестування, полягає в тому, що SQL містить дуже розумний і складний оптимізатор, який може ігнорувати індекс, якщо він вирішить, що сканування таблиці швидше, або може використовувати сортування, або організовувати сторінки пам'яті, однак це добре любить.


Отже, це здається, що якби я коли-небудь мав декілька рядків, де поле бітів дорівнює 1 (наприклад, відстежуючи "IsProcessed"), то індекс був би хорошим, оскільки він упорядкує їх за значенням, а потім зможе вибрати невеликий робочий набір дуже швидко. Якщо ви згодні, додайте це, і я прийму це.
jeremcc

2
Що я маю на увазі в своєму попередньому коментарі, це те, що це твердження: "Коли ви індексуєте бітове поле (або якийсь вузький діапазон), ви зменшуєте лише робочий набір навпіл", не відповідає дійсності, якщо розподіл сильно зважено до одного значення. Але мені подобається решта вашої відповіді, тому якщо ви це виправите, я прийму це.
jeremcc

1
Зроблено. Я думав, що на мільйон рядків бітове поле має 50% розподілу, але ви праві, що для певного проблемного простору це може значно зменшити робочий набір.
Джефф Кокс

Варто переглянути плани виконання з індексом і без нього, і побачити, чи використовується індекс і чи фактично знижує вартість ваших запитів. Легко та науково!
onupdatecascade

Що щодо індексації бітового поля + іншого поля? Напр. у журналі журналу веб-активностей можна буде індексувати часову позначку, але інший корисний індекс може бути у полі "IsHTTPS" + часова мітка, щоб швидко переглянути всі дії https. Це також буде неефективним?
інгредієнт_15939

19

Я просто натрапив на це питання іншим способом. Якщо припустити, що ваше твердження про те, що лише декілька записів припускає значення 1 (а це саме ті, що вас цікавлять), відфільтрований індекс міг би стати хорошим вибором. Щось на зразок:

create index [IX_foobar] on dbo.Foobar (FooID) where yourBitColumn = 1

Це створить істотно менший індекс, який оптимізатор досить розумний для використання, коли це предикат у вашому запиті.


1
Варто зазначити, що предикат у запиті має бути жорстко закодований до значення у відфільтрованому індексі. Якщо ви передаєте значення в параметрі yourBitColumn = @value, оптимізатор не може визначити, чи є відфільтрований індекс корисним.
geofftnz

2
Існують способи навколо цього, але ти маєш рацію; оптимізатору потрібна гарантія під час компіляції, що значення будь-яких предикатів, що відповідають фільтрованому предикату індексу, є статичними / інваріантними, оскільки це завдання оптимізатора створити загальний план, який буде працювати для будь-якого набору параметрів.
Бен Тул

9

100 мільйонів записів, лише декілька з полем бітів встановлено в 1? Так, я думаю, що індексація бітового поля безумовно пришвидшить запит на біт = 1 записи. Ви повинні отримати логарифмічний час пошуку з індексу, а потім торкатися лише декількох сторінок із записами біт = 1. В іншому випадку вам доведеться торкнутися всіх сторінок 100-мільйонної таблиці записів.

Знову ж таки, я, безумовно, не експерт по базі даних, і могло пропустити щось важливе.


8

Якщо ваш розподіл досить відомий і незбалансований, наприклад, 99% рядків - це біт = 1, а 1% - біт = 0, коли ви робите пункт WHERE з бітом = 1, повне сканування таблиці буде приблизно в той же час, що і індексне сканування. Якщо ви хочете отримати швидкий запит, де bit = 0, найкращий спосіб, який я знаю, - це створити відфільтрований індекс, додавши пункт WHERE bit = 0. Таким чином, цей індекс зберігатиме лише рядок 1%. Тоді виконання біта WHERE = 0 просто дозволить оптимізатору запитів вибрати цей індекс, і всі рядки з нього будуть бітними = 0. Ви також маєте перевагу мати дуже малу кількість дискового простору, порівнявши повний індекс на біт .


2
Якщо 99% рядків є біт = 1, оптимізатор повинен ігнорувати індекс і виконувати сканування таблиці. Використання індексу насправді буде гірше, ніж сканування таблиці, принаймні на обертовому накопичувачі, більше вводу / виводу та безперервного зчитування з диска. Фільтрований індекс (Postgres еквівалент: частковий індекс) - це шлях. Я здогадуюсь, тому що через роки після запитання ця відповідь не отримала заслужених голосів.
Андрій Лазар

7

Хоча я не думаю, що я би індексував ПОСЛІДНО бітний стовпець, дуже часто включати бітові стовпці як частину складного індексу.

Простим прикладом може бути індекс на ACTIVE, LASTNAME замість просто прізвища, коли ваша програма майже завжди шукає активних клієнтів.


7
У прикладі, який ви навели, я б більше схильний ставити LastName першим. Це залежить від конкретного навантаження на запит, але загалом, що спочатку має більш вибірковий стовпець, значить, індекс є більш імовірним.
Мітч Пшеничний

7

Якщо ви цього не прочитали, недавно Джейсон Массі написав статтю, в якій обговорював цю саму тему.

http://statisticsio.com/Home/tabid/36/articleType/ArticleView/articleId/302/Never-Index-a-BIT.aspx

Редагувати: нове місце розташування статті - http://sqlserverpedia.com/blog/sql-server-bloggers/never-index-a-bit

Машина зворотного звороту для раніше розміщеної статті "Нове": http://web.archive.org/web/20120201122503/http://sqlserverpedia.com/blog/sql-server-bloggers/never-index-a-bit/

Нове місце розташування Pedia SQL Server - Toadworld, де є нова стаття від Кеннета Фішера, яка обговорює цю тему:

http://www.toadworld.com/platforms/sql-server/b/weblog/archive/2014/02/17/dba-myths-an-index-on-a-bit-column-will-never-be- used.aspx

зворотна машина: http://web.archive.org/web/20150508115802/http://www.toadworld.com/platforms/sql-server/b/weblog/archive/2014/02/17/dba-myths-an -index-on-a-bit-column-ніколи не буде використаний.aspx


ця стаття більше не видно
Homer6,

@ Homer6 Я додав посилання на те, як виглядає новий будинок для цієї статті.
Джефф

Нове посилання переходить на домашню сторінку Toad World.
N Захід

Знайшов статтю за допомогою машини Wayback і знайшов нову пов’язану статтю. Сподіваюся, це допомагає.
Джефф

2

Звичайно, варто, особливо якщо вам потрібно отримати дані за цим значенням. Це було б аналогічно використанню розрідженої матриці замість використання звичайної матриці.

Тепер за допомогою SQL 2008 ви можете використовувати функції розділення, і ви можете фільтрувати дані, що надходять в індекс. Недоліком для попередніх версій було б те, що індекс буде зроблений для всіх даних, але це можна оптимізувати, зберігаючи цікаві значення в окремій групі файлів.


2

Як говорили інші, ви захочете це виміряти. Я не пам'ятаю, де я це читав, але колонка повинна мати дуже високу кардинальність (близько 95%), щоб індекс був ефективним. Вашим найкращим тестом на це було б скласти індекс та вивчити плани виконання для значень 0 та 1 поля BIT. Якщо ви побачите операцію пошуку індексу в плані виконання, ви знаєте, що ваш індекс буде використаний.

Вашим найкращим способом дій буде тестування основної таблиці SELECT * FROM WHERE BitField = 1; запитуйте і повільно виробляйте функціонал звідси покроково, поки у вас не з’явиться реалістичний запит для вашої програми, вивчаючи план виконання з кожним кроком, щоб переконатися, що пошук індексу все ще використовується. Справді, немає гарантії, що цей план виконання буде використовуватися у виробництві, але є хороший шанс, що він буде.

Деяку інформацію можна знайти на форумах sql-server-performance.com та у статті, що посилається


Важлива не стільки кардинальність колонки в цілому. Це вибірковість пункту WHERE. Тож якщо є кілька стовпців зі значенням 1, це все одно може бути добре проіндексувати. Якщо це 50/50 (наприклад, чоловік / жінка), то не так варто.
ВВ.

2

"Я пам'ятаю, як читав в один момент, що індексувати поле з низькою кардинальністю (мала кількість чітких значень) насправді не варто робити"

Це тому, що SQL Server майже завжди вважає його більш ефективним просто сканувати таблицю, ніж читати індекс. Тому в основному ваш індекс ніколи не звикне, і його марно підтримувати. Як говорили інші, у складному індексі це може бути нормально.


2

Якщо ваша мета - зробити запит для записів, де значення бітового поля дорівнює "1" швидше, ви можете спробувати індексований вигляд базової таблиці, який містить лише записи, де ваше бітове поле дорівнює "1". У корпоративному виданні, якщо запит може використовувати індексований вигляд замість вказаної таблиці для поліпшення продуктивності запиту, він буде використовувати перегляд. Теоретично це збільшило б швидкість вибору запитів, які шукають записи лише зі значенням бітового поля '1'.

http://www.microsoft.com/technet/prodtechnol/sql/2005/impprfiv.mspx

Все це передбачає, що ви є Microsoft SQL Server 2005 Enterprise. Те саме може стосуватися і 2008 року, я не знайомий з цією версією.


2

Якщо ви хочете дізнатися, чи має індекс такі ефекти, які ви бажаєте: протестуйте і протестуйте ще раз.

Як правило, ви не хочете, щоб індекс, який не звужує вашу таблицю достатньо, через витрати на підтримку індексу. (вартість> прибуток). Але якщо індекс у вашому випадку скоротить таблицю навпіл, ви можете отримати щось, але поставити його на стіл. Все залежить від точного розміру / структури вашої таблиці та того, як ви її використовуєте (кількість читань / записів).


1

Само по собі, ні, оскільки це призводить до дуже мало вибірковості. У складі складного індексу. цілком можливо, але лише після інших стовпців рівності.


1

Не можна індексувати бітове поле в SQL Server 2000, як було зазначено в Книгах Онлайн:

біт

Цілі дані типу 1, 0 або NULL.

Зауваження

Стовпці бітового типу не можуть містити в них індекси.

Так, якщо у вас є лише кілька рядків з мільйонів, індекс допоможе. Але якщо ви хочете зробити це в цьому випадку, вам потрібно зробити стовпець a tinyint.

Примітка : Менеджер підприємств не дозволить вам створити індекс у бітовій колонці. Якщо ви хочете, ви все ще можете вручну створити індекс на бітовій колонці:

CREATE INDEX IX_Users_IsActiveUsername ON Users
(
   IsActive,
   Username
)

Але SQL Server 2000 фактично не використовуватиме такий індекс - виконує запит, де індекс був би ідеальним кандидатом, наприклад:

SELECT TOP 1 Username 
FROM Users
WHERE IsActive = 0

Натомість SQL Server 2000 зробить сканування таблиці, діючи так, ніби індекс навіть не існує. Якщо ви зміните стовпець на крихітний SQL Server 2000, буде здійснено пошук індексу. Також наступний незакритий запит:

SELECT TOP 1 * 
FROM Users
WHERE IsActive = 0

Він виконає пошук за індексом, за яким слід шукати закладку.


SQL Server 2005 має обмежену підтримку індексів на бітових стовпцях. Наприклад:

SELECT TOP 1 Username 
FROM Users
WHERE IsActive = 0

викликає пошук індексу через індекс покриття. Але не охоплений випадок:

SELECT TOP 1 * 
FROM Users
WHERE IsActive = 0

не спричинить пошук індексу з подальшим пошуком закладок, він виконає сканування таблиці (або кластерне сканування індексу), замість того, щоб виконувати пошук індексу, за яким слід шукати закладку.

Перевірено шляхом експерименту та прямого спостереження.


FYI - Студія управління SQL Server 2005 дозволяє вам це робити.
jeremcc

Моя копія SQL Server 2000 дозволила мені встановити індекс на бітовій колонці.
Кіббі

Моя копія SQL Server 2000 не дозволяє мені встановлювати індекс на бітовій колонці.
Ян Бойд

1

дуже пізня відповідь ...

Так, це може бути корисно команді SQL CAT (оновлено, консолідовано)


1
Здається, посилання зараз мертве. Однак, як видається, ця публікація була закріплена разом з кількома іншими в електронній книзі . Посилання на цей розділ починається на сторінці 86. Електронну книгу можна завантажити з електронних книг SQLCAT.com за посиланням «Посібник SQLCAT до реляційного двигуна».
mwolfe02

0

Це звичайний запит? Це, можливо, варто того, щоб шукати "кілька" записів, але не допоможе вам сильно в інших рядках. Чи є інші способи ідентифікації даних?


0

Кардинальність - це один із факторів, інший - наскільки добре індекс розділяє ваші дані. Якщо у вас є приблизно половина півтора і половина 0, то це допоможе. (Якщо припустити, що цей індекс - кращий шлях вибору, ніж інший індекс). Однак як часто ви вставляєте та оновлюєте? Додавання індексів для продуктивності SELECT також завдає шкоди продуктивності INSERT, UPDATE та DELETE, тому майте це на увазі.

Я б сказав, якщо від 1 до 0 (або навпаки) не краще 75% до 25%, не турбуйтеся.


1
Я б не погодився. Якщо ваш розподіл становить 50/50, то ви ніколи не використовуєте індекс, оскільки це було б просто швидше зробити сканування таблиці. Однак якщо у вас є лише 5, 1 значення та 1 мільйон 0 значень, дуже ймовірно буде використовувати індекс під час пошуку 1.
Kibbee

0

виміряйте час відгуку до і після і подивіться, чи варто це; теоретично це повинно підвищити ефективність запитів із використанням індексованих полів, але це дійсно залежить від розподілу правдивих / хибних значень та інших полів, що беруть участь у запитах, які вас турбують


0

Йен Бойд має рацію, коли каже, що ви не могли це зробити через Enterprise Manager для SQL 2000 (див. Його примітку щодо створення його за допомогою T-SQL.


0

Ви повинні бути розумними, щоб запитувати, ви повинні знати значення навантаження на ваш стовпець, якщо навантаження true у вашій системі більше, і ви хочете перевірити всі справжні значення, записавши запит, щоб перевірити, чи не помилково. Це допоможе багато , це просто хитрість.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.