Каковы оптимальные методы использования GUID в качестве первичного ключа, особенно в отношении производительности?

336

У меня есть приложение, которое использует GUID в качестве первичного ключа почти во всех таблицах, и я прочитал, что существуют проблемы с производительностью при использовании GUID в качестве первичного ключа. Честно говоря, я не видел никаких проблем, но я собираюсь запустить новое приложение, и я все еще хочу использовать GUID в качестве первичных ключей, но я думал об использовании составного первичного ключа (GUID и, возможно, другое поле .)

Я использую GUID, потому что ими удобно и легко управлять, когда у вас есть разные среды, такие как «производственная», «тестовая» и «dev» базы данных, а также для миграции данных между базами данных.

Я буду использовать Entity Framework 4.3 и хочу назначить Guid в коде приложения, прежде чем вставлять его в базу данных. (т.е. я не хочу, чтобы SQL генерировал Guid).

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

Вааа
источник
20
Вопрос не предполагается. Если ваш ПК кластеризован, то почти каждая вставка может вызвать разделение страницы. В современных версиях SQL Server это было «исправлено» с помощью NEWSEQUENTIALID (), но это теряет преимущество возможности вычислять его заранее. Я настоятельно рекомендую вам ознакомиться с GUID в других местах, поскольку это слишком широкий вопрос и, вероятно, вызовет религиозную битву, которая будет продолжаться часами ...
Аарон Бертран,
4
Я бы также добавил, что слово « сервер» неоднозначно, я хочу назначить Guid на стороне сервера (не хочу, чтобы SQL создавал GUID) .
Эрик Филипс
Этот вопрос имеет сходство с этим «sql-server-guid-sort-алгоритмом-почему» stackoverflow.com/questions/7810602/…
Клинтон Уорд

Ответы:

495

Идентификаторы GUID могут показаться естественным выбором для вашего первичного ключа - и, если вам действительно нужно, вы, вероятно, можете поспорить, чтобы использовать его для ПЕРВИЧНОГО КЛЮЧА таблицы. Что я настоятельно рекомендую не делать, так это использовать столбец GUID в качестве ключа кластеризации , что SQL Server делает по умолчанию, если вы специально не запретите это делать.

Вам действительно нужно держать в стороне две проблемы:

  1. первичный ключ является логической конструкцией - один из ключей - кандидатов , которые однозначно и надежно идентифицируют каждую строку в таблице. Это может быть что угодно, на самом деле - INT, GUID, строка - выбрать то , что делает большинство смысла для вашего сценария.

  2. ключ кластеризации (столбец или столбцы , которые определяют «кластерный индекс» на столе) - это физическое хранение связанных вещь, и здесь, небольшой, стабильный, постоянно увеличивающийся тип данных ваш лучший выбор - INTили BIGINTкак ваш опция по умолчанию.

По умолчанию первичный ключ в таблице SQL Server также используется в качестве ключа кластеризации, но это не обязательно должно быть так! Я лично видел значительное увеличение производительности, когда разбивал предыдущий основанный на GUID первичный / кластерный ключ на два отдельных ключа - первичный (логический) ключ в GUID и ключ кластеризации (упорядочения) в отдельном INT IDENTITY(1,1)столбце.

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

Да, я знаю - есть newsequentialid()в SQL Server 2005 и более поздних версиях - но даже это не совсем и полностью последовательно и, следовательно, также страдает от тех же проблем, что и GUID- чуть менее заметно.

Затем следует рассмотреть еще одну проблему: ключ кластеризации в таблице будет добавлен к каждой записи в каждом некластеризованном индексе в вашей таблице - таким образом, вы действительно хотите убедиться, что он как можно меньше. Как правило, более INT2 миллиардов строк должно быть достаточно для подавляющего большинства таблиц - и по сравнению с GUIDключом кластеризации вы можете сэкономить сотни мегабайт хранилища на диске и в памяти сервера.

Быстрый расчет - использование INTпротив в GUIDкачестве первичного ключа и ключа кластеризации:

  • Базовая таблица с 1 000 000 строк (3,8 МБ против 15,26 МБ)
  • 6 некластеризованных индексов (22,89 МБ против 91,55 МБ)

ИТОГО: 25 МБ против 106 МБ - и это только на одном столе!

Еще немного пищи для размышлений - отличный материал Кимберли Триппа - прочитайте его, прочитайте снова, переварите! Это на самом деле индексное Евангелие SQL Server.

PS: конечно, если вы имеете дело с несколькими сотнями или несколькими тысячами строк - большинство из этих аргументов не окажут большого влияния на вас. Однако: если вы попадаете в десятки или сотни тысяч строк или начинаете считать в миллионах - тогда эти точки становятся очень важными и очень важными для понимания.

Обновление: если вы хотите, чтобы ваш PKGUIDстолбец был вашим основным ключом (но не ключом кластеризации), а другой столбец MYINT( INT IDENTITY) - вашим ключом кластеризации - используйте это:

CREATE TABLE dbo.MyTable
(PKGUID UNIQUEIDENTIFIER NOT NULL,
 MyINT INT IDENTITY(1,1) NOT NULL,
 .... add more columns as needed ...... )

ALTER TABLE dbo.MyTable
ADD CONSTRAINT PK_MyTable
PRIMARY KEY NONCLUSTERED (PKGUID)

CREATE UNIQUE CLUSTERED INDEX CIX_MyTable ON dbo.MyTable(MyINT)

По сути: вам просто нужно явно указать PRIMARY KEYограничению, что оно NONCLUSTERED(в противном случае оно создается как кластерный индекс по умолчанию), а затем вы создаете второй индекс, который определяется какCLUSTERED

Это будет работать - и это допустимый вариант, если у вас есть существующая система, которую необходимо «перепроектировать» для повышения производительности. Для новой системы, если вы начинаете с нуля и у вас нет сценария репликации, я бы всегда выбирал в ID INT IDENTITY(1,1)качестве своего кластерного первичного ключа - гораздо более эффективный, чем все остальное!

marc_s
источник
2
Это отличный ответ, я бы хотел упомянуть, что возможность генерации ключа перед вставкой часто бывает полезна. Использование «newsequentialid ()» может помочь с кластеризацией, но для этого требуется дополнительный обходной путь к SQL. Поэтому еще одно преимущество подхода «суррогатного ключа» заключается в том, что вы можете генерировать новые идентификаторы на стороне клиента с меньшим количеством проблем фрагментации индекса.
Эндрю Текен
2
То, как я это прочитал, состоит в том, что, имея как некластеризованный столбец uniqueidentifier, так и столбец int identity, FK также должны быть uniqueidentifier? Если вы сделаете это, когда вы действительно будете использовать столбец идентификации напрямую или нет?
pinkfloydx33
2
Маленький вопрос, должен ли теперь GUID использоваться для объединений или int id? Мой инстинкт подсказывает мне, что надо использовать GUID, но я не вижу технической проблемы при использовании int id ...
Николас Белли
3
@marc_s, но в сценарии репликации, если столбец int идентичен, не следует ли нам использовать GUID, поскольку столбец int может повторяться на разных устройствах?
Николас Белли,
6
@Kipei: главная проблема в том, ЕСЛИ у вас такая естественная ценность - тогда да, вы можете использовать ее в качестве первичного ключа. НО : такие значения, как, DATETIMEнапример, НЕ полезны для ключа кластеризации, поскольку они имеют точность только 3,33 мс, и, следовательно, могут существовать дубликаты. Таким образом , в таком случае, вы * еще нужно INT IDENTITYвместо этого - следовательно, я обычно использую , что по умолчанию, так как frmo моих 20+ лет опыта, действительно использовать естественный ключ вряд ли когда - либо действительно существует ....
marc_s
51

Я использую GUID в качестве PK с 2005 года. В этом мире распределенных баз данных это абсолютно лучший способ объединения распределенных данных. Вы можете запустить и забыть таблицы слияния, не беспокоясь о совпадении целых чисел между объединенными таблицами. Соединения GUID могут быть скопированы без каких-либо забот.

Это мои настройки для использования GUID:

  1. PK = GUID. Идентификаторы GUID индексируются аналогично строкам, поэтому для таблиц с высокими строками (более 50 миллионов записей) может потребоваться разбиение таблиц или другие методы повышения производительности. SQL Server становится чрезвычайно эффективным, поэтому проблемы с производительностью становятся все менее и менее применимыми.

  2. PK Guid является некластеризованным индексом. Никогда не кластеризируйте индекс GUID, если это не NewSequentialID. Но даже тогда перезагрузка сервера приведет к серьезным перебоям в заказе.

  3. Добавьте ClusterID Int к каждой таблице. Это ваш КЛАСТЕРНЫЙ индекс ... который заказывает ваш стол.

  4. Объединение по ClusterID (int) более эффективно, но я работаю с 20-30 миллионами таблиц записей, поэтому объединение по GUID заметно не влияет на производительность. Если вы хотите максимальной производительности, используйте концепцию ClusterID в качестве основного ключа и присоединитесь к ClusterID.

Вот моя таблица электронной почты ...

CREATE TABLE [Core].[Email] (
    [EmailID]      UNIQUEIDENTIFIER CONSTRAINT [DF_Email_EmailID] DEFAULT (newsequentialid()) NOT NULL,        
    [EmailAddress] NVARCHAR (50)    CONSTRAINT [DF_Email_EmailAddress] DEFAULT ('') NOT NULL,        
    [CreatedDate]  DATETIME         CONSTRAINT [DF_Email_CreatedDate] DEFAULT (getutcdate()) NOT NULL,      
    [ClusterID] INT NOT NULL IDENTITY,
    CONSTRAINT [PK_Email] PRIMARY KEY NonCLUSTERED ([EmailID] ASC)
);
GO

CREATE UNIQUE CLUSTERED INDEX [IX_Email_ClusterID] ON [Core].[Email] ([ClusterID])
GO

CREATE UNIQUE NONCLUSTERED INDEX [IX_Email_EmailAddress] ON [Core].[Email] ([EmailAddress] Asc)
Роберт Дж. Хорошо
источник
Не могли бы вы объяснить ограничение PK_Email? Почему у вас есть ... Некластеризованный (EmailID ASC) вместо ... Некластеризованный (ClusterID ASC)?
Фил
2
Вы ставите. Две основные вещи, происходящие с индексами: 1. Кластеризация по ClusterID - Упорядочивает вашу таблицу на диске (0% фрагментации). 2. NonClustered on EmailID - индексирует поле EmailID для ускорения поиска идентификатора GUID. Поиск в поле GUID ведет себя как string-ish, поэтому поиск по EmailID будет медленным без индекса.
Роберт Дж. Хорошо
@ RobertJ.Хорошо. Я уже видел этот метод, то есть добавление суррогатного ключа int в кластер. Но я не могу найти нигде, который показывает увеличение производительности при наличии кластеризованного индекса с суррогатным ключом по сравнению с использованием кучи. Есть ли у вас ссылки на данные тестов?
Дейл К
1
Привет @DaleBurrell, кластеризованный индекс для предотвращения фрагментации таблицы. Повышение производительности происходит по мере естественного увеличения таблицы на диске с низкой фрагментацией.
Роберт Дж. Хорошо
@ RobertJ.Good Это веб-приложение? Что вы используете в urls / hrefs? Guid или Int?
Дариол
10

В настоящее время я разрабатываю веб-приложение с EF Core, и вот шаблон, который я использую:

Все мои классы (таблицы) и INT PK и FK. У меня есть дополнительный столбец с типом Guid (сгенерированный конструктором c #) с некластеризованным индексом.

Все соединения таблицы в EF управляются через клавиши int, а весь доступ извне (контроллеры) осуществляется с помощью направляющих.

Это решение позволяет не показывать клавиши int на URL-адресах, но позволяет поддерживать чистоту и порядок в модели.

EricImhauser
источник
Есть ли что-то, что вам нужно сделать, чтобы сконфигурировать целое число pK как кластеризованное, например аннотации данных, или оно просто настраивается автоматически?
Аллен Ван
Какое название недвижимости вы используете для Guid?
Тронг Фан
3

Если вы используете GUID в качестве первичного ключа и создаете кластерный индекс, тогда я предлагаю использовать для него значение по умолчанию NEWSEQUENTIALID ()

AnandPhadke
источник
Почему ты бы так поступил?
Genuinefafa
3

Эта ссылка говорит, что это лучше, чем я мог, и помог в принятии решений. Я обычно выбираю int в качестве первичного ключа, если у меня нет особой необходимости, и я также позволяю SQL-серверу автоматически генерировать / поддерживать это поле, если у меня нет особых причин не делать этого. В действительности, проблемы производительности должны определяться на основе вашего конкретного приложения. Здесь есть много факторов, в том числе ожидаемый размер БД, правильная индексация, эффективные запросы и многое другое. Хотя люди могут не согласиться, я думаю, что во многих сценариях вы не заметите различий ни с одним из этих вариантов, и вам следует выбрать то, что больше подходит для вашего приложения, а что позволяет разрабатывать проще, быстрее и эффективнее (если вы никогда не завершите приложение какая разница для остальных :).

https://web.archive.org/web/20120812080710/http://databases.aspfaq.com/database/what-should-i-choose-for-my-primary-key.html

PS Я не уверен, почему вы бы использовали Composite PK или какую выгоду вы считаете, что это даст вам.

Matt
источник
Полностью согласен!! Но это означает, что если у меня есть GUID в качестве PK или Composite PK с GUID, и другие поля будут такими же, верно?
VAAA
1
PK (индекс) будет состоять из двух столбцов, но если у вас нет какой-либо конкретной бизнес-причины для этого, это кажется ненужным.
Мэтт
1
Кстати, этот вопрос является одним из самых поляризующих и обсуждаемых вопросов, и поэтому чрезвычайно трудно получить ответ, на который вы будете чувствовать себя на 100% комфортно. Любой метод идет с компромиссами, так что удачи :)
Мэтт
1

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

  1. https://www.sqlskills.com/blogs/kimberly/disk-space-is-cheap/
  2. https://www.sqlskills.com/blogs/kimberly/guids-as-primary-keys-andor-the-clustering-key/
Асрар Ахмад Эхсан
источник
0

Наличие последовательных идентификаторов значительно облегчает хакеру или майнеру компрометации ваш сайт и данные. Имейте это в виду при выборе ПК для сайта.

DaBlue
источник
Можете ли вы предоставить какую-либо логику или доказательства для подтверждения этого утверждения? Я пытаюсь понять, как последовательный идентификатор может поставить под угрозу безопасность.
Джонаглон
Конечно, если вы знаете, что идентификационные номера целые, вы можете угадывать последовательные записи в БД. Таким образом, если вы запрашиваете один элемент, вы можете сказать, что следующий элемент - pk + 1. Если у вас есть случайные GUIDS, он не будет следовать шаблону. Было бы почти невозможно запросить другие записи, кроме той, которую вы запрашивали ранее (И знаете ПК).
DaBlue
1
Если хакер может запросить вашу базу данных, вы уже взломаны, я не вижу, как последовательные идентификаторы ухудшают ситуацию.
Ионаглон
1
Если пользователь может переключить 1012 на другой номер и увидеть данные, которые ему не нужны, тогда возникает очень серьезная проблема безопасности, эта проблема не вызвана выбором первичного ключа, а усугубляется этим. Я понимаю вашу точку зрения, спасибо за разъяснение.
Ионаглон
2
Вы можете использовать GUID, чтобы найти запись на веб-странице, которая не является PK таблицы. Использование параметра запроса на веб-сайте не должно определять структуру вашей схемы БД. PK не имеет ничего общего с вводом и параметрами в пользовательском интерфейсе или бэкэнд-системе.
Панос Родитакис