Причины избегания больших значений ID

17

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

Я не думаю, что она права относительно причины инфляции ID (коллега, который может ответить на это, находится в отпуске, поэтому мы не знаем наверняка), но давайте предположим, что это так. Она сказала, что не хотела бы использовать столбец bigint и хотела бы, чтобы мы прекратили автоинкрементирование столбца ID и писали код на стороне сервера, который выбирает первое «неиспользуемое» целое число и использует его в качестве идентификатора.

Я аспирант по информатике с небольшим практическим опытом, занимающий младшую должность разработчика. Она имеет многолетний опыт управления всеми базами данных нашей организации и разработки большинства из них. Я думаю, что она неверна в этом случае, что bigint ID нечего бояться, и что имитация функциональности СУБД пахнет антипаттерном. Но я пока не доверяю своему суждению.

Каковы аргументы за и против каждой позиции? Какие плохие вещи могут случиться, если мы используем bigint, и каковы опасности переосмысления функции автоинкрементации колеса ? Есть ли третье решение, которое лучше, чем одно? Каковы могут быть ее причины для того, чтобы избежать инфляции по номиналу? Мне также интересно услышать о прагматических причинах - может быть, идентификаторы bigint работают в теории, но на практике вызывают головные боли?

Приложение не должно обрабатывать очень большие объемы данных. Я сомневаюсь, что он достигнет 10 000 реальных записей в течение следующих нескольких лет.

Если это имеет какое-то значение, мы используем сервер Microsoft SQL. Приложение написано на C # и использует Linq to SQL.

Обновить

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

Меня не очень беспокоит реальная причина высоких идентификаторов. Если мы не сможем найти его самостоятельно, я мог бы задать другой вопрос. Что меня интересует, так это понять процесс принятия решений в этом случае. Для этого, пожалуйста, предположите, что приложение будет писать 1000 записей в день, а затем удалит 9999 из них . Я почти уверен, что это не так, но это то, во что верил мой босс, когда она сделала свой запрос. Итак, в этих гипотетических обстоятельствах, каковы будут плюсы и минусы использования bigint или написания нашего собственного кода, который будет присваивать идентификаторы (таким образом, чтобы повторно использовать идентификаторы уже удаленных записей, чтобы гарантировать отсутствие пробелов)?

Что касается фактической причины, я сильно подозреваю, что это потому, что мы когда-то писали код для импорта данных из другой базы данных, в качестве доказательства концепции, что более поздняя миграция может быть выполнена в определенной степени. Я думаю, что мой коллега на самом деле создал несколько тысяч записей во время импорта, а затем удалил их. Я должен подтвердить, так ли это на самом деле, но если это так, то даже не нужно предпринимать какие-либо действия.

rumtscho
источник
Смотрите сообщение С.М. Ахасана Хабиба на codeproject.com/Tips/668042/…
RLF
Вы можете уточнить? Новые идентификаторы просто получают значения> 10000? Или это новые идентификаторы имеют пробелы в 10000? А сколько идентификаторов потребуется в будущем приложении?
user2338816
1
Относительно поиска первого неиспользуемого идентификатора есть глава, посвященная именно этому, в книге Билла Карвина «Антипаттерны SQL». Так что да, это, безусловно, можно рассматривать как антипаттерн!
Томас Падрон-Маккарти

Ответы:

24

Не видя код, довольно сложно окончательно сказать, что происходит. Хотя, скорее всего, IDENTITYзначение кэшируется, что приводит к появлению пробелов в значении после перезапуска SQL Server. См. Https://stackoverflow.com/questions/17587094/identity-column-value-suddenly-jumps-to-1001-in-sql-server для некоторых хороших ответов и информации об этом.

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

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

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

Другой вариант, если вы используете SQL Server 2012+, - это использование SEQUENCEобъекта для получения значений идентификатора для столбца. Однако вам необходимо настроить последовательность так, чтобы она не кэшировала значения. Например:

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

В ответ на негативное восприятие вашего босса «больших» чисел, я бы сказал, какая разница? Предполагая, что вы используете INTполе, с IDENTITY, вы можете фактически начать IDENTITYat 2147483647и "увеличить" значение на -1. Это не имеет абсолютно никакого значения для потребления памяти, производительности или используемого дискового пространства, поскольку 32-разрядное число составляет 4 байта, независимо от того, является ли оно 0или 2147483647. 0в двоичном виде - 00000000000000000000000000000000при хранении в 32-битном INTполе со знаком. 2147483647является01111111111111111111111111111111- оба числа занимают одинаковое количество места, как в памяти, так и на диске, и оба требуют точно одинакового количества операций процессора для обработки. Гораздо важнее правильно составить код приложения, чем помнить о фактическом числе, хранящемся в ключевом поле.

Вы спрашивали о плюсах и минусах: (а) использования столбца идентификаторов с большей емкостью, например BIGINT, или (б) развертывания собственного решения для предотвращения пропусков идентификаторов. Чтобы ответить на эти вопросы:

  1. BIGINTа не INTкак тип данных для рассматриваемого столбца. Использование a BIGINTтребует удвоенного объема памяти, как на диске, так и в памяти для самого столбца. Если столбец является индексом первичного ключа для используемой таблицы, каждый некластеризованный индекс, присоединенный к таблице, также будет хранить BIGINTзначение, в два раза больше INT, и снова, как в памяти, так и на диске. SQL Server хранит данные на диске в страницах размером 8 КБ, где количество «строк» ​​на «страницу» зависит от «ширины» каждой строки. Так, например, если у вас есть таблица с 10 столбцами, каждый из INTкоторых может содержать примерно 160 строк на страницу. Если эти столбцы, где вместоBIGINTстолбцы, вы сможете хранить только 80 строк на странице. Для таблицы с очень большим количеством строк это явно означает, что число операций ввода-вывода, необходимых для чтения и записи таблицы, в этом примере будет удвоенным для любого заданного числа строк. Конечно, это довольно экстремальный пример - если бы у вас была строка, состоящая из одного столбца INTили BIGINTстолбца и одного NCHAR(4000)столбца, вы (упрощенно) получили бы одну строку на страницу, независимо от того, использовали ли вы INTили a BIGINT. В этом сценарии это не будет иметь большого значения.

  2. Прокручивая свой собственный сценарий, чтобы избежать пробелов в столбце ID. Вам нужно было бы написать свой код таким образом, чтобы определение значения «следующего» идентификатора для использования не конфликтовало с другими действиями, происходящими с таблицей. Что-то в духе 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 (как объяснено в первой ссылке, предоставленной Максом).

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

Забавно, что MS заявляет, что обе альтернативы (либо флаг трассировки 272, либо новый объект SEQUENCE) могут повлиять на производительность.

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

Lmu92
источник
Я, вероятно, сформулировал свой вопрос неправильно, но я не очень заинтересован в поиске причины. Существует высокая вероятность того, что это либо что-то, что больше не появится (результаты теста), либо неправильное проектное решение в приложении, которое может быть решено за пределами базы данных. Цель состояла в том, чтобы понять, почему опытный администратор баз данных посчитал бы высокие идентификаторы плохими или хуже, чем использование собственного управления идентификацией.
rumtscho
2

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

Использование BigInt в вашем случае является излишним, оно также может вызвать проблемы, если ваше приложение или данные будут доступны через ODBC (например, перенесены в Excel или MS Access и т. Д.), Bigint плохо переводит большинство драйверов ODBC в настольные приложения.

Что касается GUIDS, помимо дополнительного дискового пространства и дополнительного ввода-вывода, существует огромная проблема, заключающаяся в том, что они по своей структуре не последовательны, поэтому, если они являются частью отсортированного индекса, можно догадаться, что каждая вставка будет требует, чтобы индекс был восстановлен. --Джим

jimo3
источник
Хорошие замечания по поводу GUID, если вы не используете NEWSEQUENTIALID () - я все же согласен, что нет веских причин использовать их в этом вопросе.
Макс Вернон
1

Есть ли разрыв между используемыми значениями? Или начальные значения 10.000 и с этого момента все добавляют 1? Иногда, если число будет присвоено клиентам, начальное число будет больше нуля, например, скажем, 1500, поэтому клиент не осознает, что система «новая».

Недостаток использования bigint вместо smallint состоит в том, что, поскольку bigint использует «больше дискового пространства», при чтении диска вы читаете меньше дисковых блоков для каждого диска. Если ваше пространство строк мало, то это может быть недостатком, если нет, то это не имеет большого значения. Также не имеет большого значения, если вы не запрашиваете много ресурсов одновременно, и если у вас есть правильные индексы.

И, как сказано в другом ответе, если вы беспокоитесь о нехватке индексов, тогда вам не стоит беспокоиться, smallint может справиться, если у вас нет миллионера. Изобретать механизм «восстановления идентификаторов» стоит дорого, он добавляет точки сбоя и усложняет программное обеспечение.

С уважением

ctutte
источник
2
OP видит пробелы при перезапуске службы. Это из-за этой проблемы . Кроме того, я не думаю, что smallint является хорошим компромиссом в краткосрочной перспективе для работы, которая потребуется, чтобы исправить это позже.
Аарон Бертран
@AaronBertrand на самом деле, я боюсь, что другие неправильно поняли это, когда они предложили такую ​​возможность. Я совершенно уверен, что это не является причиной больших чисел, но даже если бы это было так, я не пытался найти причину, а узнать, какие аргументы могут быть за и против предложенных решений. Смотрите мое обновление для деталей.
rumtscho
@rumtscho на самом деле этот ответ подчеркивает хорошую мысль, даже если он не отвечает непосредственно на ваш вопрос: «Изобретать механизм« восстановления идентификаторов »стоит дорого и добавляет точки сбоя и сложность в программное обеспечение».
Доктор J
@DoktorJ Я согласен с тобой. Я был человеком, который проголосовал за ответ :) Просто хотел прояснить недоразумение, поэтому я оставил свой первый комментарий.
rumtscho
1

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

  1. Если предварительное тестирование увеличило значения идентификаторов - тогда ваши другие комментарии об ожидаемом количестве записей также подтолкнули бы меня к предложению меньшего типа ключа. Честно говоря, я бы также подумал, возможно ли сбросить последовательность и перенумеровать существующие записи, если тест был не в своем роде для текущего предполагаемого использования таблицы (большинство сочло бы это перебором - «это зависит»).

  2. Если большинство записей, записанных в таблицу, будут удалены вскоре после того, как я склонен рассмотреть возможность использования двух таблиц; временная таблица, в которой записи не хранятся в течение длительного времени, и другая, в которой хранятся только записи, которые мы будем создавать постоянно. Опять же, ваши ожидания относительно количества долгосрочных записей подсказывают мне использование меньшего типа для ключевого столбца, и несколько записей в день вряд ли вызовут у вас проблему с производительностью, чтобы «переместить» запись из одной таблицы в другую подобным образом. один. Я подозреваю, что это не ваш сценарий, но представьте, что веб-сайт покупок может предпочесть сохранить Basket / BasketItem, и когда заказ фактически размещен, данные перемещаются в набор Order / OrderItem.

Обобщить; на мой взгляд, BIGINT не обязательно бояться, но, откровенно говоря, излишне велики для многих сценариев. Если таблица никогда не становится большой, вы никогда не поймете, что при выборе типа было слишком много ... но если у вас есть таблицы с миллионами строк и множеством столбцов FK, которые являются БОЛЬШИМИ, когда они могли бы быть меньше - тогда вы можете пожелать типы были выбраны более консервативно (учитывайте не только ключевые столбцы, но и все ключевые столбцы, все резервные копии, которые вы храните и т. д.). Дисковое пространство не всегда дешевое (рассмотрим SAN-диск в управляемых местах - то есть дисковое пространство арендуется).

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

Наконец, технологии, которые люди используют, могут значительно повлиять на их ожидания и ответы. Некоторые инструменты с большей вероятностью могут вызвать разрывы в диапазонах, например, путем предварительного бронирования диапазонов идентификаторов для каждого процесса. Напротив, @DocSalvager предлагает тщательную проверяемую последовательность, которая, кажется, отражает точку зрения вашего босса; Лично я никогда не требовал такого уровня полномочий, хотя общее правило, согласно которому идентичности являются последовательными и обычно без пробелов, часто было невероятно полезным для меня в ситуациях поддержки и анализа проблем.

Nij
источник
1

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

Используя bigintкак личность и живя с пробелами:

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

Сверните свои собственные:

  • Ваша команда разработчиков будет выполнять всю работу по разработке и исправлению ошибок навсегда.
  • Вы просто хотите заполнить пробелы в хвосте или в середине, тоже? Дизайнерские решения спорить.
  • при каждой записи будут возникать сильные блокировки, чтобы предотвратить одновременный процесс получения одного и того же нового идентификатора или разрешение конфликтов постфактум .
  • В худшем случае вам придется обновить каждую строку в таблице, чтобы закрыть пробелы, если rowid = 1 будет удален. Это увеличит параллелизм и производительность, что связано со всеми каскадными обновлениями внешних ключей и т. Д.
  • ленивый или готовый заполнить пробел? Что происходит с параллелизмом, когда это происходит?
  • вам придется читать новый идентификатор перед любой записью = дополнительная загрузка.
  • Индекс будет необходим для столбца идентификатора для эффективного поиска пропусков.
Майкл Грин
источник
0

Если вы действительно заинтересованы в достижении верхнего порога INT для ваших PK, рассмотрите возможность использования GUID. Да, я знаю, что это 16 байтов против 4 байтов, но диск дешевый.

Вот хорошее описание плюсов и минусов.

Тим Гойер
источник
4
+1, потому что это решение, но см . Комментарий Аарона к ответу Макса по той причине, что «диск дешев» не является причиной для использования GUID без тщательного взвешивания вариантов.
Джек Дуглас
1
Вот лучшая статья от
Аарон Бертран
О, и, конечно, остерегайтесь разбиения страницы от NEWID ()
Макс Вернон
1
Мой босс, кажется, возражает против высоких ценностей только потому, что они смотрят высоко. Я надеюсь, что этот вопрос покажет мне больше возможных возражений, но если это один из ее главных аргументов, она, вероятно, отреагирует еще более негативно на GUID.
rumtscho
1
@rumtscho Скажите своему боссу, что суррогатное число - это просто бессмысленное число («размер» числа не имеет значения) и что пробелы в последовательности естественны и в основном неизбежны.
Аарон Бертран
0

Первичные ключи СУБД (столбец обычно называется «ИД»)
Нельзя избежать пробелов в автоинкрементных столбцах (полях) СУБД. Они в первую очередь предназначены для создания уникальных ПК. Для повышения производительности основные продукты распределяют их по частям, поэтому механизмы автоматического восстановления для различных сбоев нормальной работы могут привести к тому, что числа останутся неиспользованными. Это нормально.


Непрерывные последовательности Если вам нужен непрерывный порядковый номер, такой, который часто ожидают пользователи, это должен быть отдельный столбец, который назначается программно и не должен быть PK. Таким образом, все эти 1000 записей могут иметь одинаковый номер в этом столбце.

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

Повторное использование значений ключей для несвязанных записей делает базу данных недействительной.
Использование «первого неиспользуемого целого числа» повышает вероятность того, что в какой-то момент в будущем число будет повторно использовано для записей, не связанных с оригиналом. Это делает базу данных ненадежной как точное представление фактов. Это основная причина, по которой механизмы автоинкрементации специально разработаны для того, чтобы никогда не использовать значение повторно.

DocSalvager
источник