Причини уникання великих значень ідентифікатора


17

Ми працюємо над веб-додатком, ще недоступним для користувачів. Мій бос зауважив, що новостворені записи отримують ідентифікатор понад 10 000, хоча у нас є лише 100 записів. Вона припустила, що веб-інтерфейс чомусь створює в 100 разів більше тимчасових записів, ніж фактичні (і видаляє їх), і що це може привести нас до вичерпання діапазону протягом декількох місяців після виходу.

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

Я студент з інформатики, маючи практичний досвід, виконуючи роль молодшого розробника. Вона має багаторічний досвід управління всіма базами даних нашої організації та розробкою більшості з них. Я думаю, що в цьому випадку вона помилкова, що ідентифікатор bigint не варто боятися, і що імітуючи функціональність СУБД, пахне антипатерном. Але я ще не вірю в своє судження.

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

Не очікується, що програма обробляє дуже великі обсяги даних. Сумніваюсь, що вона досягне 10 000 фактичних записів протягом найближчих кількох років.

Якщо це має значення, ми використовуємо сервер Microsoft SQL. Додаток написано на C # і використовує Linq для SQL.

Оновлення

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

Мене не дуже хвилює реальна причина високих посвідчень. Якщо ми не можемо знайти його самостійно, я можу задати інше питання. Що мене цікавить, це зрозуміти процес прийняття рішення в цій справі. Для цього слід припустити, що програма записуватиме 1000 записів на день, потім видаляючи 9999 з них . Я майже впевнений, що це не так, але в це повірив мій начальник, коли вона зробила своє прохання. Отже, за цих гіпотетичних обставин, які б були плюси і мінуси або в застосуванні bigint, або написанні власного коду, який присвоює ідентифікатори (таким чином, що повторно використовує ідентифікатори вже видалених записів, щоб переконатися у відсутності прогалин)?

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


Дивіться публікацію С.М. Ахасана Хабіба за адресою codeproject.com/Tips/668042/…
RLF

Ви можете уточнити? Чи отримують нові ідентифікатори просто значення> 10000? Або це те, що нові ідентифікатори мають прогалини в 10000? І скільки ідентифікаторів, за оцінками, потрібно в майбутньому житті додатків?
user2338816

1
Щодо пошуку першого невикористаного ідентифікатора, є глава про саме те, що в книзі Білла Карвіна "SQL Antipterns". Так що так, це, безумовно, можна розглядати як антипатерн!
Томас Падрон-Маккарті

Відповіді:


24

Не бачачи коду, досить важко сказати остаточно, що відбувається. Хоча, швидше за все, IDENTITYзначення кешується, викликаючи прогалини у значенні після перезапуску SQL Server. Дивіться /programming/17587094/identity-column-value-suddenly-jumps-to-1001-in-sql-server для отримання гарних відповідей та інформації про це.

Просте INTполе може містити значення до 2,147,483,647. Ви можете фактично запустити значення ідентичності з -2,147,483,648, давши повних 32 біт значень. 4 мільярди різних цінностей. Я дуже сумніваюся, що у вас не вистачить цінностей, які потрібно використовувати. Припускаючи , що ваш додаток буде споживати тисячі значень для кожного рядка фактичної доданої, вам необхідно створити близько 12 000 рядків в день кожен день , щоб втекти з ідентифікаторів в протягом 6 місяців , які передбачають ви почали IDENTITYзначення 0, і були з допомогою INT. Якщо ви використовували BIGINT, вам доведеться почекати 21 мільйон століть, перш ніж у вас вичерпаються значення, якщо ви пишете 12 000 рядків на день, споживаючи 1000 "значень" на рядок.

Сказавши все це, якщо ви хочете використовувати BIGINTяк тип даних поля ідентичності, у цьому, звичайно, немає нічого поганого. Це дасть вам для всіх намірів і цілей необмежену кількість цінностей для використання. Різниця в продуктивності між INT і BIGINT практично не існує на сучасному 64-бітному апаратному забезпеченні, і вкрай бажана порівняно з примірником NEWID()для створення GUID.

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

Іншим варіантом, якщо припустити, що ви використовуєте SQL Server 2012+, буде використовувати SEQUENCEоб’єкт для отримання значень ідентифікатора для стовпця. Однак вам знадобиться налаштувати послідовність, щоб не кешувати значення. Наприклад:

CREATE SEQUENCE dbo.MySequence AS INT START WITH -2147483648 INCREMENT BY 1 NO CACHE;

Відповідаючи на негативне сприйняття вашим начальником "високих" цифр, я б сказав, яка різниця це має? Припустимо, що ви використовуєте INTполе з значком an IDENTITY, ви насправді можете запустити значення IDENTITYat 2147483647і "збільшення" на -1. Це не мало б різниці у використанні пам'яті, продуктивності чи дисковому просторі, оскільки 32-бітове число становить 4 байти, незалежно від того, є це 0чи 2147483647. 0у двійковому - це 00000000000000000000000000000000коли зберігається в 32-бітному підписаному INTполі. 2147483647є01111111111111111111111111111111- обидва числа займають точно однакову кількість місця як в пам'яті, так і на диску, і обидва вимагають точно однакової кількості операцій з процесором для обробки. Набагато важливіше правильно розробити код програми, ніж нав’язувати фактичну кількість, що зберігається в ключовому полі.

Ви запитували про плюси та мінуси будь-якого (a) використання стовпця з ідентифікатором більшої ємності, наприклад, a BIGINT, або (b) прокатування власного рішення, щоб запобігти прогалинам ідентифікаторів. Щоб відповісти на ці проблеми:

  1. BIGINTзамість INTяк тип даних для відповідного стовпця. Для використання BIGINTпотрібно два рази більше пам’яті, як на диску, так і в пам'яті для самого стовпця. Якщо стовпець є індексом первинного ключа для відповідної таблиці, кожен некластеризований індекс, приєднаний до таблиці, також буде зберігати BIGINTзначення вдвічі більше INT, ніж в пам'яті та на диску. SQL Server зберігає дані на диску на 8 КБ сторінок, де кількість "рядків" на "сторінку" залежить від "ширини" кожного рядка. Так, наприклад, якщо у вас є таблиця з 10 стовпцями, кожен з яких - INTви, приблизно, зможете зберігати 160 рядків на сторінці. Якщо ті колонки, де замість цьогоBIGINTстовпців, ви зможете зберігати лише 80 рядків на сторінці. Для таблиці з дуже великою кількістю рядків це очевидно означає, що введення-виведення, необхідне для читання та запису таблиці, буде в цьому прикладі подвійним для будь-якої кількості рядків. Зрозуміло, це досить екстремальний приклад - якби у вас був рядок, що складається з одного INTчи BIGINTстовпця та одного NCHAR(4000)стовпця, ви мали б (спрощено) отримувати один рядок на сторінці, використовували ви INTчи a BIGINT. У цьому сценарії це не мало б помітної різниці.

  2. Розгортання власного сценарію для запобігання прогалин у стовпці ідентифікаторів. Вам потрібно буде написати свій код таким чином, щоб визначення "наступного" значення ідентифікатора для використання не суперечило іншим діям, що відбуваються з таблицею. Щось по лінії SELECT TOP(1) [ID] FROM [schema].[table]наївно приходить у голову. Що робити, якщо кілька акторів намагаються одночасно записати нові рядки в таблицю? Два учасники могли легко отримати однакове значення, що призвело до конфлікту між записами. Вирішення цієї проблеми вимагає серіалізації доступу до таблиці, зниження продуктивності. Про цю проблему написано багато статей; Я залишу його читачеві, щоб здійснити пошук на цю тему.

Висновок тут: вам потрібно зрозуміти свої вимоги і правильно оцінити як кількість рядків, так і ширину рядків, а також вимоги щодо одночасності вашої заявки. Як завжди, це залежить ™.


4
+1, але я б не відкидав вимоги до місця BIGINT. Не стільки для місця на диску, скільки для вводу / виводу та простору, витраченого на пам'ять. Ви можете багато чого компенсувати за допомогою стиснення даних, тому ви не відчуваєте тяжкого ступеня типу BIGINT, поки не перевищите 2 мільярди. В ідеалі вони б просто вирішили цю проблему (я вагаюся, називаючи це помилкою) - хоча люди не повинні піклуватися про прогалини, і хоча люди не повинні перезавантажувати свої сервери 15 разів на день, у нас є обидва ці сценарії. досить поширений і часто в тандемі.
Аарон Бертран

3
Дуже дійсні бали, Аарон, як завжди. Я б схильний до використання INT в будь-якому випадку, оскільки BIGINT - це майже загальний перевибір, якщо вони не очікують величезної кількості рядків.
Макс Вернон

Тип даних BIGINT для стовпця ідентифікатора не матиме великого впливу на пам'ять, якщо у вас не буде одночасно сотні тисяч або більше в пам'яті. Вже тоді це, мабуть, невелика частка загального розміру рядків.
user2338816

2
@ user2338816 ось в чому справа - якщо таблиця стане великою, пам’яті буде багато. А оскільки стовпець ідентичності, як правило, є кластерним ключем, то це зайві 4 байти для кожного рядка в кожному індексі. Чи буде це важливо у кожному окремому випадку? Ні. Чи слід його ігнорувати? Абсолютно не. Здається, ніхто не дає змоги розширити масштабність, поки не пізно.
Аарон Бертран

3
Хоча , якщо ви робите є законне сподівання , що може знадобитися bigintвам , ймовірно , подякуйте себе за рішення , що заздалегідь , а не необхідності додати це до таблиці з мільярдами рядків.
Мартін Сміт

6

Основне завдання, яке потрібно зробити, - це знайти першопричину, чому поточне значення таке високе.

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

Починаючи з SQL2012, найімовірніша причина пов’язана з декількома перезапусками SQL Engine (як пояснено в першому наданому посиланні Max).

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

"Смішно", що MS заявляє, що обидві альтернативи (або прапор сліду 272, або новий об'єкт SEQUENCE) можуть впливати на продуктивність.

Це може бути найкращим рішенням використовувати BIGINT замість INT просто для того, щоб бути надійною стороною для покриття MS наступними "поліпшеннями" ...


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

2

Румцо, якщо ви створюєте лише 1000 рядків на день, вирішити мало, - використовуйте тип даних INT з полем Identity і виконайте це. Проста математика говорить, якщо ви дасте додатку 30-річний життєвий цикл (навряд чи), ви могли б мати 200 000 рядків на день і все ще знаходитись у позитивному діапазоні чисел типу даних INT.

Використання BigInt є надмірним у вашому випадку, це також може спричинити проблеми, якщо ваш додаток або дані будуть доступні через ODBC (наприклад, внесені в Excel або MS Access тощо), Bigint не добре переводить більшість драйверів ODBC на додатки для настільних ПК.

Що стосується GUIDS, окрім додаткового місця на диску та додаткового вводу / виводу, існує величезна проблема, що вони за конструкцією не є послідовними, тому, якщо вони є частиною відсортованого індексу, ви можете здогадатися, що кожна вставка збирається вимагають вдатися до індексу. - Джім


Хороший момент щодо GUID, якщо ви не використовуєте NEWSEQUENTIALID () - Я все одно погоджуюся, немає жодної великої причини використовувати їх, очевидно, у цьому питанні.
Макс Вернон

1

Існує розрив між використаними значеннями? Або вихідні значення - 10 000, і з цього моменту всі додають 1? Іноді, якщо кількість буде надана клієнтам, початкове число перевищує нуль, скажімо, наприклад, 1500, тож клієнт не розуміє, що система "нова".

Недолік використання bigint замість smallint полягає в тому, що, оскільки bigint використовує "більше місця на диску", під час читання диска ви читаєте менше блоків на кожному диску. Якщо ваш ряд рядків невеликий, то це може бути недоліком, якщо ні, це не має великого значення. Крім того, це не має великого значення, якщо ви не запитуєте за великою кількістю ресурсів одночасно і якщо у вас є відповідні індекси.

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

З повагою


2
ОП бачить прогалини у перезапуску служби. Це через це питання . Крім того, я не думаю, що маленький коледж - це хороший компроміс у короткий термін за роботу, яку потрібно буде виправити пізніше.
Аарон Бертран

@AaronBertrand насправді, боюся, інші неправильно зрозуміли це, коли запропонували таку можливість. Я впевнений, що це не є причиною високих показників, але навіть якби це було, я не намагався знайти причину, але дізнався, які аргументи можуть бути за та проти запропонованих рішень. Докладніше див. У моєму оновлення.
румчо

@rumtscho насправді ця відповідь підкреслює хороший момент, навіть якщо він безпосередньо не стосується вашого питання: "Винайдення механізму" відновлення ідентифікаторів "є дорогим і додає балам відмов і складності програмному забезпеченню."
Doktor J

@DoktorJ Я згоден з вами. Я була людиною, яка підтримала відповідь :) Просто хотіла прояснити непорозуміння, тому я залишила свій перший коментар.
румчо

1

Якби я був вашим начальником, мене б найбільше цікавили причини несподівано високих значень Id ... те, як я це бачу, для кожного з двох сценаріїв, які ви окреслили:

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

  2. ЯКЩО більшість записів, записаних у таблицю, видаляються незабаром після того, як я схиляюсь до використання двох таблиць; тимчасова таблиця, де записи не зберігаються довгостроково, та інша, де зберігаються лише записи, які ми будемо створювати постійно. Знову ж таки, ваші очікування щодо кількості довгострокових записів пропонують мені використовувати менший тип для вашого ключового стовпця, і кілька записів на день навряд чи спричинить проблему з продуктивністю для переміщення запису з однієї таблиці в іншу подібну один. Я підозрюю, що це не ваш сценарій, але уявіть, що веб-сайт для покупок може вважати за краще підтримувати Basket / BasketItem, а коли замовлення фактично розміщено, дані переміщуються у набір Order / OrderItem.

Підсумовувати; на мою думку, БІГІНТів не обов’язково боятися, але вони, відверто кажучи, великі для багатьох сценаріїв. Якщо таблиця ніколи не стає великою, ти ніколи не зрозумієш, що на ваш вибір типу була надмірна кількість ... але коли у вас є таблиці з мільйонами рядків і безліччю стовпців FK, які є BIGINT, коли вони могли бути меншими - тоді ви можете побажати типи були вибрані більш консервативно (враховуйте не лише ключові стовпці, а й усі стовпці клавіш переднього плану, і всі резервні копії, які ви зберігаєте тощо). Місце на диску не завжди є дешевим (врахуйте диск SAN в керованих місцях - тобто місце на диску орендовано).

По суті, я закликаю уважно розглянути вибір вашого типу даних завжди, а не іноді . Ви не завжди будете правильно прогнозувати шаблони використання, але я думаю, що ви приймете кращі рішення як правило, тоді завжди припускаючи, що «більший, тим краще». Як правило, я вибираю найменший тип, який може містити необхідний і розумний діапазон значень, і я з радістю вважатиму INT, SMALLINT і навіть TINYINT, якщо я думаю, що це значення, ймовірно, вписується в цей тип в найближчому майбутньому. Більш дрібні типи навряд чи будуть використовуватися з стовпцями IDENTITY, однак вони можуть із задоволенням використовуватися для таблиць пошуку, де ключові значення встановлюються вручну.

Нарешті, технології, якими користуються люди, можуть значно вплинути на їхні очікування та відповіді. Деякі інструменти, швидше за все, можуть спричинити розриви в діапазонах, наприклад, попередньо забронювавши діапазони тотожностей за процес. На противагу @DocSalvager пропонує ретельну послідовність аудиторії, яка, здається, відображає точку зору вашого начальника; Я особисто ніколи не вимагав такого рівня авторитету - хоча загальне правило про те, що особистість є послідовною і взагалі без прогалин, часто була неймовірно корисною мені в ситуаціях підтримки та аналізі проблем.


1

які плюси та мінуси використання bigint або написання власного коду, який присвоює ідентифікатори (таким чином, що повторно використовує ідентифікатори вже видалених записів, щоб уникнути пропусків)?

Використання bigintяк ідентичності та життя з прогалинами:

  • це все вбудований функціонал
  • Ви можете бути впевнені, що це спрацює нестандартно
  • з цього часу вона витратить місце int все-таки дасть вам приблизно 2 млн. днів; більше сторінок доведеться читати та писати; індекси можуть стати глибшими. (Однак у цих обсягах це не викликає значних проблем).
  • сурогатний ключ стовпця мається на увазі безглуздий, тому прогалини в порядку. Якщо це показано користувачам, а прогалини трактуються як значні, то ви робите це неправильно.

Згорніть своє:

  • ваша команда розробників буде вести всю роботу з розробки та усунення помилок.
  • ви просто хочете заповнити прогалини в хвості або посередині теж? Дизайнерські рішення сперечаються.
  • при кожному написанні доведеться виправляти сильні блокування, щоб запобігти одночасним процесам придбання того ж нового ідентифікатора чи вирішенню конфліктів постфакту .
  • в гіршому випадку вам доведеться оновлювати кожен рядок таблиці, щоб закрити прогалини, якщо rowid = 1 буде видалено. Це призведе до збільшення одночасності та продуктивності, що стосується всіх каскадних оновлень зовнішніх ключів тощо
  • ледачий чи прагнутий заповнення прогалин? Що відбувається з одночасністю, поки це відбувається?
  • вам доведеться прочитати новий ідентифікатор перед будь-яким записом = додаткове завантаження.
  • для ефективного пошуку розриву потрібен індекс у стовпці id.

0

Якщо ви дійсно стурбовані тим, щоб досягти верхнього порогу INT для ПК, подумайте про використання GUID. Так, я знаю, що це 16 байт проти 4 байтів, але диск дешевий.

Ось хороша характеристика плюсів і мінусів.


4
+1, оскільки це рішення, але дивіться коментар Аарона щодо відповіді Макса з причини, чому "диск дешевий" - це не привід використовувати GUID, не ретельно зважуючи параметри.
Джек Дуглас

1
Ось краще записування з експерта індексу та архітектури SQL Server, а не розробника: sqlskills.com/blogs/kimberly/disk-space-is-cheap
Аарон Бертран

О, і, звичайно, остерігайтеся розбиття сторінок від NEWID ()
Макс Вернон

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

1
@rumtscho Скажіть своєму начальникові, що сурогатне число - це просто безглузде число ("розмір" числа не має значення) і що прогалини в послідовності є природними і в основному неминучими.
Аарон Бертран

0

Первинні ключі RDBMS (стовпець, що зазвичай називається "ID")
Пробіли неможливо уникнути в стовпцях (полях) для самовдосконалення RDBMS. Вони в першу чергу призначені для створення унікальних ПК. Для продуктивності основні продукти розподіляють їх по партіях, тому автоматичні механізми відновлення для різних відхилень у звичайній роботі можуть призвести до того, що кількість не використовується. Це нормально.

Нерозривні послідовності
Коли вам потрібен нерозривний номер послідовності, такий, як часто очікують користувачі, це повинен бути окремий стовпець, який призначається програмно і не повинен бути ПК. Таким чином, ці 1000 записів можуть мати однакову кількість у цьому стовпці.

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

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

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