Евангелие от GUID

http://weblogs.asp.net/wwright/archive/2007/11/04/the-gospel-of-the-guid-and-why-it-matters.aspx
  • Перевод
Разбираясь с новым Visual C# 2008 (он настолько бесплатный для начинающих разработчиков, что я не удержался), нашел новое для себя слово в науке и технике — GUID.

Привожу пример интересной, как мне кажется, статьи, призывающей использовать глобально-уникальные идентификаторы во всех сферах народного хозяйства. Статья, в основном про .NET и прочий микрософт, но, думаю, будет полезна многим здесь, ибо реализации GUID есть почти во всех современных БД и языках (включая mySQL и PHP ;).

ПС: Если будет интересно, то выложу перевод второй части, где автор отвечает на комменты к первой статье.


Евангелие от GUID


В Евангелие от GUID есть только одна заповедь:

I. Всегда используй GUID для уникальной идентификации строки таблицы.


При приеме новых сотрудников в команду это — одно из первых правил, которым я их обучаю. Почти всегда поначалу они смотрят на меня с видом щенка с торчащими ушами и склоненной набок головой, как бы говоря «как это?»

Поэтому я показываю им, что в каждой таблице есть поле, являющееся уникальным идентификатором, а в большинстве случаев и первичным ключом и, обычно, кластеризованным индексом. Например, в таблице Employee у нас будет поле EmployeeGUID, уникальный идентификатор и кластеризованный первичный ключ. Тут обычно я получаю в ответ взгляд «опа, мой новый босс идиот, но я не хочу, чтобы он знал, что я так думаю». Потом приходят вопросы и комментарии вида «но я обычно делаю так»:
  1. Я использую int
  2. Я не пользуюсь GUID, потому что они такие большие
  3. Разве вы не понимаете, как трудно искать запись по такому id, в случае с int это гораздо проще
  4. Ну вы же не станете их использовать в таблице, реализующей отношение многие-ко-многим?
  5. Что произойдет, если GUID'Ы закончатся?
  6. Я не уверен, что они не будут повторяться
  7. Я никогда ни о чем подобном не слышал, поэтому, скорее всего это плохая мысль.
  8. А это не ухудшит производительность?

И начинается моя миссия по обращению. У меня хорошая статистика по обращению «думающих» людей, поэтому призываю вас прочитать эту статью и, возможно, вы тоже станете верующим в GUID!

1) Мне не нужно совершать дополнительных выборок, а это — увеличение производительности!


Существует множество причин для использования GUID в качестве первичного ключа. Главная для меня напрямую связана с тем, как я строю объектные модели. Я предпочитаю создавать «new» экземпляр объекта без совершения выборки. Так, создавая объект Order (заказ) я не буду обращаться к базе данных для получения OrderID (OrderGUID в моем мире), как я бы делал в случае с int OrderID. На этом уровне еще не слишком впечатляет, да? Подумайте вот о чем: я создаю объект Order с OrderGUID, потом объекты OrderLineItem (строки заказа) с OrderLineItemGUID без ЕДИНОГО разрыва обращения к БД. В случае с int я бы сделал 11 обращений.

2) Объединение данных настолько просто, что получается даже у Мак-разработчиков!


Следующая причина всегда использовать GUID — объединение данных (merging), оказывавшееся необходимым бессчетное количество раз. До того как я увидел свет, я тоже использовал int или что-то еще, чтобы сделать строку уникальной, но когда мне приходилось сливать данные из раных источников, я делел специальные преобразования.

Представьте себе:

DB1 (Клиент 1):
Order (таблица заказов)
OrderID = 1
CustomerID = 1

DB2 (Клиент 2):
Order
OrderID = 1
CustomerID = 1


Если Клиент 1 приобретает Клиента 2 и мне нужно слить их данные в единую БД, мне придется поменять чьи-то OrderID и CustomerID на какие-нибудь int значения, которые не используюся, после чего сделать update большому количеству записей, а, возможно и поплясать с бубном и с опорными значениями (seed values). Умножьте это на десятки таблиц, учтите миллионы строк данных, и у бедитесь, что передо мной стоит ДЕЙСТВИТЕЛЬНО сложная задача, которая потребует дофига тестирования после написания SQL и/или кода.

Однако, если я следую Евангелию от GUID:

DB1 (Клиент 1):
Order (таблица заказов)
OrderID: {C290A815-BAAA-4455-82EA-F18F5D0CEF2E}
CustomerID: {52335ABA-2D8B-4892-A8B7-86B817AAC607}

DB2 (Клиент 2):
Order
OrderID: {79E9F560-FD70-4807-BEED-50A87AA911B1}
CustomerID: {60FA3045-BAE8-4526-87A2-DF22ED5F093B}


В этом случае, все, что нужно сделать сводится к обычной вставке всех строк из одной БД в другую. Никаких преобразований, никаких замороченных тестов, просто, удобно и действенно. Недавно мне пришлось проделать эту операцию с БД двух моих клиентов AT&T и Cingular. Все «преобразование» заняло 45 минут.

Другой простой пример: представьте, что ваши клиенты часто работают удаленно в оффлайне, и вам приходится закачивать их данные в общую БД при подключении. Теперь это проще, чем у ребенка конфету отнять… © Если вы верите в GUID. Вы можете легко таскать данные между базами.

3) Типо-независимость


Третья причина, по которой я верю в GUID — это то, что я называю «типо-независимость» (Type Ignorance — это игра слов, построенная на Type Inference В .NET 3.5). Суть ее в том, что мне не важно, как хранятся данные в каждой таблице БД. Представьте, что у нас есть продукт, находящийся в коммерческом использовании уже некоторое время. В БД есть таблицы Customer, Order, OrderLineItem, Product (Товар) и Vendor (Поставщик). Теперь выяснилось, что нужно добавить «заметки» к каждому виду объекта. Для этього достаточно просто создать таблицу
  Notes
  NoteGUID Unique Identifier
  ParentGUID Unique Identifier
  Note VarChar(500)

Теперь можно вставлять заметки в эту таблицу, используя GUID самих объектов в ParentGUID. Для получения всех заметок по конкретному товару совершается простейшая выборка из notes по его GUID.

Например, чтобы получить все заметки по поставщику, достаточно создать простую связь (join) Note.ParentGUID к Vendor.VendorGUId. Не нужны никакие индикаторы типов, не нужно выдумывать, какие таблицы связывать, не нужно кучи ссылочных таблиц, чтобы понять с каким типом объекта связана строка.

Вы удивитесь, узнав насколько часто используется этот небольшой прием. Недавно мы добавили «аудит» к одному из наших приложений, в котором хотели выяснить кто что удалял, добавлял или изменял в БД. Мы просто добавили несколько строчек кода к методу DataContext SubmitChanges() (мы используем только LINQ в этом приложении) для создания соответствующей записи в таблице аудита. При создании нового объекта или типа в приложении запись в эту таблицу происходит автоматически, что позволяет нам не париться написанием специального «аудиторского» кода при добавлении новых типов данных в приложении.

4) Удар, удар, еще удар...(с)


Существует много менее очевидных причин для использования GUID, но есть одна, которую я не предвидел заранее и за которую я благодарю GUID, ибо он и только он спас миллионы долларов моему клиенту… да, я сказал МИЛЛИОНЫ!

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

На нашем сайте была развернута рабочая БД клиента и тестовая БД, слегка устаревшая копия рабочей (на пару месяцев). В процессе тестирования кто-то на их стороне увидел один из наших тестовых файлов с очередью оплат и, не долго думая, скормил их платежному серверу. Ну, дальше вы поняли… Клиент заплатил куче действительных поставщиков контента дважды (один раз по реальному запросу, второй раз — по тестовому), а также еще и не совсем нормальным поставщикам (например тем, что уже не размещали рекламу, ведь тестовая БД устарела на пару месяцев). Вот так, без каких-либо косяков с моей стороны, я получил ужасную помойку в данных… ну по крайней мере так думал мой клиент. Однако, поскольку все мои записи о выплатах имели GUID, я мог легко выделить те записи, что пришли из тестовой базы, чтобы отменить платежи по ним. Представьте, если бы я использовал INT, у меня не было бы способа узнать из какой базы пришел запрос PaymentID = 1000, например.

Ну так как же это помогло спасти миллионы? Просто… умножьте тысячи запросов на штраф за отмену платежа ($20-30). И еще на три, поскольку такая ошибка повторилась три раза!

Ну а есть ли недостатки у GUID?


Если кратко, то да, есть. Однако они, настолько незначительны, что не могут изменить моего мнения. Наиболее очевидный из них — это написание SQL запросов вручную (кгда надо что-то найти).

SELECT * FROM ORDER WHERE ORDERID = 12

гораздо легче, чем

SELECT * FROM ORDER WHERE ORDERGUID = ‘{45F57B42-38A4-46ce-A180-6DE0E7051178}'

Еще один недостаток — небольшое снижение производительности у связей, построенных на базе gUID, по сравнению с INT. Но по моему опыту даже при использовании таблиц с многомиллионным количеством строк, это никогда не становилось проблемой. Несколько миллисикунд задержки — небольшая цена за все прелести GUIDа.

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

— Published Sunday, November 04, 2007 10:35 PM by wwright
Filed under: .NET, ASP.NET, Tips and Tricks, LINQ, SQL Server
Поделиться публикацией
Комментарии 9
    0
    Использовал GUID когда делал плагин для Firefox.

    Вопрос: GUID'ы же могут повториться! Вот тогда произойдет глобальный пипец.
    Единственное что, это вести базу созданных GUID и проверять на существование, а если GUID есть - перегенерять его и снова проверять.

    Как-то загадочно. Кажется, я чего-то не понимаю.
      0
      Особенно проверять нет смысла, база данных должна это автоматически делать. То есть, если запись не вставилась, пробуем еще раз.

      Но, опять же, вероятность такого исхода пренебрежимо мала.
      0
      Чорт, вот оно что:

      GUID (Globally Unique Identifier) — представляет собой статистически уникальный 128-битный идентификатор. Его главная особенность — уникальность, которая позволяет создавать расширяемые сервисы и приложения без опасения конфликтов, вызванных совпадением идентификаторов. Хотя уникальность каждого отдельного GUID не гарантируется, общее количество уникальных ключей настолько велико (2128 или 3,4028
        0
        Парсер лохе.

        http://ru.wikipedia.org/wiki/GUID
          0
            0
            Во-первых: "небольшое снижение производительности у связей, построенных на базе gUID" - наличие или отсутствие "не" в первом слове зависит очень много от чего. Просто нагенерить 70 миллионов гуидов - это уже целое дело. А это, например, всего-навсего трудовой день в NYSE.

            Во-вторых: часто на основе ID строятся какие-нибудь коды, которые где-то печатаются. За номера заказа в виде guid надо расстреливать из крупнокалиберного пулемета не отходя от кассы, имхо. Чтобы это прочувствовать - попробуйте прочитать номер заказа девочке-операторше по мобилке, ага.

            В-третьих: проблема foreign data purge, возникающая при сливании наборов данных из разных источников, практически не решается на основе идентификаторов - нужны natural keys. То, что в БД аптеки я под номером 1234567890, а у страхового агента - под номером 0987654321 ни чем не помогает, простите.

            Ну и, наконец, в последних: слово "Евангелие" плохо сочетается со 128 битами.
              0
              За формирование чего бы то ни было печатающегося на основании суррогатного ключа записи в БД — надо расстреливать ещё суровее.
              0
              GUID'ы при генерации, под Виндой по крайней мере, используют текущее время. WinAPI использует таймер и при генерации КАЖДОГО GUID'а делает некоторую задержку по времени прежде чем отдать обатно его значение. Так что, для БД оно самое оно! В своё время шутили, что генерация GUID'а ещё один недокументированный способ получить гарантированную задержку в несколько мс при исполнении программы.
                0
                кстати, стоит упомянуть, что 128 бит — это 16 байт, либо — судя по Вашему примеру — строка из 38 символов, включая скобки и дефисы. Оракловый тип Number имеет примерно такое же максимальное ограничение — он занимает до 16 байт, и декларирует хранение до 38 значимых десятичных знаков. Но числа можно (до 15 знаков) тянуть на клиент как Double, а GUID-ы — врядли, встроенных типов данных на 16 байт кажись нету, и придётся тянуть строки по 32 или по 38 знаков, и наверно тогда уж их же и хранить, чтоб не конвертить их туда-обратно каждый раз.

                В GUID, генерённых подряд в винде, меняющаяся часть начинается где-то знака с 8-го… хотя, на дальних этапах работы БД с обычными числовыми идентификаторами — меняющаяся часть тож в правую часть строки уползает… Но сравниваются они как числа, наверняка тоже справа, с младших разрядов. Интересно, как это может повлиять на эффективность индекса?

                И ещё стоит помянуть (из википедии): «генерируя 1 триллион ключей каждую наносекунду, перебрать все возможные значения удастся лишь за 10 миллиардов лет.»

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое