Про GUID-ы и INT-ы

Последнее время я часто думаю о том, чтобы начать использовать в новых таблицах в качестве суррогатного первичного ключа не привычные большинству целые числа INT IDENTITY (1,2,3, …), а глобальные уникальные идентификаторы GUID (5e09962e-1de5-48a6-a71e-fb9c5ec58b01, 5aeae3ec-fccf-4739-bde6-09d3577fa121, …). Большинств коллег с некоторым недопониманием и вялым сопротивлением отнеслись к моей инициативе, поэтому я решил написать этот пост в котором расскажу о плюсах данного подхода и совсем немного (я же всё-таки предвзят) упомяну о минусах.

Начну с минусов

Даже визуально заметно, что GUID гораздо «длиннее» чем число. Это приводит сразу к двум проблемам.

  • Во-первых, GUID-ы тяжело использовать человеку, т.е. просто написать список идентификаторов в секции WHERE по памяти уже не удастся, придётся копировать.
  • Во-вторых, GUID действительно занимает в четыре раза больше места, чем INT. При простом хранении данных в базе это не так уж страшно, но может стать проблемой, когда возникнет необходимость добавить очередное поле в индекс. Мой, пусть не очень большой, но всё-таки опыт подсказывает, что «пухлые» индексы возникают в основном в ненормализованных БД, так что вторая проблема на самом деле вызвана не столько GUID-ами как таковыми, сколько неправильными архитектурными решениями.
  • В-третьих, почти все коллеги обращают внимание на тот факт, что таблицу нельзя упорядочить по столбцу с GUID-ам, получив тем самым псевдовремя. На это замечание у меня сразу два возражения: во-первых, псевдовремя порочно по сути своей (всегда можно использовать настоящее время), во-вторых, столбцу с псевдоключом можно задать значение по умолчанию равное NEWSEQUENTIALID(), тогда до перезагрузки сервера значения в данном столбце таблицы будут возрастать.

Теперь давайте о плюсах

GUID не зря называют глобальным уникальным идентификатором. Его значения статистически уникальны.

  • Т.е. мы легко можем создавать новые идентификаторы без непосредственного доступа к базе (например, в мобильном приложении) и быть уверенными, что их вставка не приведёт к конфликтам (при этом, конечно, нельзя использовать полученные значения для вставки в столбец со значением по умолчанию NEWSEQUENTIALID, но на практике это нужно относительно редко).
  • Это же свойство статистической уникальности приводит к тому, что каждый новый GUID выглядит случайным, что автоматически усложняет и делает практически невозможным подбор корректных значений в URL-адресах, используемых для подтверждения регистрации, восстановлений пароля и т.п.
  • Третьим несомненным плюсом является огромное (можно для простоты считать, что бесконечное) количество различных возможных значений. Всем кажется, что типа INT хватит всем и навсегда. Я тоже так думал, ровно до того момента, пока не заметил, как коллеги три дня искали ошибку, вызванную тем, что в таблице банально закончились новые идентификаторы.
  • Ну и наконец, ещё одно полезное свойство глобальной уникальности GUID-ов, которое окончательно склонило меня к их использованию. Если у нас в таблице t2 есть два столбца t2ID, являющийся суррогатным ключом таблицы t2, и t1ID, ссылающийся на суррогатный ключ таблицы t1, то вполне можно допустить обидную опечатку, написав в предикате соединения t2ID вместо t1ID или наоборот (я, например, часто так ошибаюсь). В случае использования INT-ов, такой очевидно ошибочный JOIN скорее всего даст на выходе непустой набор строк. Невнимательный программист не только не заметит ошибку, но и вставит полученный набор данных в какую-нибудь другую таблицу. Результат предсказать довольно тяжело, но я точно могу сказать, что ни к чему хорошему это не приведёт. Если же в столбцах хранились GUID, даже самый невнимательный программист удивится, почему простой JOIN даёт пустой набор строк. В результате в коде будет на одну ошибку меньше. По-моему, ошибку всегда лучше предотвратить, если есть такая возможность.

Как всегда, если у вас есть замечания, предложения, или вы просто хотите поспорить — милости прошу в комментарии.

P.S. Уже после публикации мне подсказали ещё несколько плюсов:

  • Говорят, что GUID-ы также незаменимы при репликации БД, но тут у меня опыта нет, поэтому утверждать не берусь.
  • Часто бывают ситуации, когда используются значения по умолчанию. Например, каждой новой заявке в техподдрежку по умолчанию присваивается статус «Ожидает назначения специалисту» (ID этого статуса 0). Вся система стабильно работает, но ровно до того момента, пока руководство не решит, что необходим новый статус по умолчанию (например, «Заявка создана» с ID 4). На первый взгляд доработать систему просто, надо в параметрах по умолчанию заменить 0 на 4, но довольно быстро вы поймёте, что значений 0 у вас в базе огромное количество, и понять, какое из них относится к заявкам, а какое к каким-то другим бизнес-процессам очень тяжело.

О вреде NULL

Давайте рассмотрим следующую ситуацию: у нас есть информация о поставщиках (название и город) и о покупателях (ФИО и город).

CREATE TABLE #Vendor
    (
      Name NVARCHAR(15)
    , City NVARCHAR(15)
    )
CREATE TABLE #Customer
    (
      Name NVARCHAR(15)
    , City NVARCHAR(15)
    )

Есть в Моске одна компания «Рога и копыта» (далее по тексту Р&К):

INSERT  #Vendor ( Name , City ) VALUES ( 'Рога и копыта' , 'Москва' )

Эта компания хочет начать экспансию в другие регионы (не в Москву), и при этом готова работать с кем угодно, главное, чтобы не из Киева. Понятно, что такие условия во многом выдуманные и бессмысленные (ну а какими ещё им быть, если они основаны на политических мотивах), но для нашего рассмотрения они подходят идеально. Запрос для поиска подходящих клиентов тривиален:

SELECT
    v.Name AS 'VendorName'
  , c.Name AS 'ClientName'
FROM
    #Vendor AS v
  , #Customer AS c
WHERE
    ( v.Name = 'Рога и копыта' )
    AND (
          ( v.City <> c.City )
          OR ( c.City <> 'Киев' )
        )

Рассмотрим одного очень богатого кочевника Тыгындыка:

INSERT  #Customer ( Name , City ) VALUES ( 'Тыгындык' , NULL )

Место его жительства нам не известно. Но давайте проверим, является ли он потенциальным клиентом для Р&К: если он живёт в Киеве, то является потенциальным клиентом по первому условию (v.City <> c.City), если же он живёт в любом(!) другом городе, то он является потенциальным клиентом по второму условию (c.City <> ‘Киев’). Тем не менее, указанный выше тривиальный запрос не вернёт нам Тыгындыка.
Всё дело в том, как работает трёхзначная логика:

Условие Результат Пояснение
v.City <> c.City UNKNOWN Любое сравнение с NULL всегда UNKNOWN, потому что NULL трактуется, как «неизвестно»
c.City <> ‘Киев’ UNKNOWN Любое сравнение с NULL всегда UNKNOWN, потому что NULL трактуется, как «неизвестно»

( v.City <> c.City )

OR ( c.City <> ‘Киев’ )

UNKNOWN Так как обе логические величины UNKNOWN, то и OR над ними тоже UNKNOWN.

( v.Name = ‘Рога и копыта’ )

AND ( ( v.City <> c.City ) OR ( c.City <> ‘Киев’ ) )

UNKNOWN Если немного подумать, станет очевидно, что TRUE И UNKNOWN тоже UNKNOWN

То есть по строгим правилам трёхзначной логики, Тыгындык не является потенциальным клиентам для Р&К, но в тоже самое время выше я показал, что с точки зрения общечеловеческой логики он вполне подходит под заданные критерии.
Т.о. бизнес потерял клиента только потому, что кто-то не написал NOT NULL при объявлении таблицы.

День 3. Москва — Минск — Москва

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

Музей валунов (общий вид)

Музей валунов (общий вид)

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

Музей валунов

Музей валунов

Сразу после этого природного музея мы отправились в знаменитую минскую библиотеку. Разумеется, книги брать мы не планировали, а хотели посетить обзорную площадку. Уже на кассе выяснилось, что до открытия площадки остался почти час. Так как это был наш последний день в Минске перед отправлением домой, время терять было нельзя: образовавшийся час мы посвятили посещению церкви всех святых. Вернее посетить её нам не удалось, так как вход в неё был закрыт, зато снаружи мы её осмотрели. Как по мне, это самый красивый православный храм, какой я видел:

Всесвятский храм

Всесвятский храм

Разве что московский храм Христа Спасителя может соперничать с всесвятским храмом по красоте, но та малая часть внутреннего убранства, которую я видел за спиной охранника, позволила мне окончательно отдать первое место именно храму всех святых. Кстати, интересной особенностью комплекса храма является памятник Моисею. До того, как я его увидел, я был уверен, что единственный в мире памятник Моисею находится в Праге, а оказалось, что он есть ещё и в Минске, и как по мне, минский памятник гораздо лучше:

Памятник Моисею

Памятник Моисею

Немного побродив по территории комплекса, мы отправились обратно к библиотеке. На этот раз площадка была открыта. Если вы меня немного знаете, то наверняка помните, что я боюсь высоты. В лифте, который поднимал нас на 22 этаж, я ехал, отвернувшись к стене, а прислонённый к стеклу iPhone, снимал весь процесс на видео (мне до сих пор страшно смотреть на эту запись). Я никак не могу понять, почему людям так нравятся виды с высоты, но раз уж вам нравится, наслаждайтесь:

Минск (панорама)

Минск (панорама)

Оставшаяся часть дня была посвящена не культурной программе, а сугубо материальным нуждам. Впечатлённый качеством белорусской одежды, я решил купить ещё и обувь. Так как в магазинах я не разбираюсь, я решил снова отправиться в ГУМ. Точно зная, что ГУМ находится на метро Октябрьская, я спустился в метро и столкнулся со сложнейшей лингвогеографической задачей — найти на линии станцию Октябрьская:

Схема метро (Минск)

Схема метро (Минск)

С помощью очень милой белорусской девочки я с этой задачей справился, купил в ГУМе несколько пар обуви, а потом ещё и в подземном ТЦ докупил несколько батонов колбасы, которые отвёз в Россию.

Последним пунктом в моей программе оставался музей ВАВ (война в Белоруссии была не Отчественная, а Айчынная).

Музей ВАВ (Миснк)

Музей ВАВ (Миснк)

К сожалению, к моему приходу музей уже был закрыт. Немного расстроенный, я отправился обратно в гостиницу, где успел провести последние часы, перемещаясь из сауны в бассейн, а из бассейна в джакузи. Уже в процессе выселения, пока оформлялись документы, я разговорился с охранником, от которого узнал очень интересную вещь. Оказывается, сотрудники гостиницы сообщают в КГБ о каждом госте. И иногда, офицеры КГБ приезжают ночью в гостиницу и без лишнего шума депортируют нежеланных людей из страны. Радует в этой ситуации меня то, что по мнению КГБ я желанный гость в Белоруссии. Надеюсь, когда-нибудь вернусь в эту чудесную страну.

День 2. Москва — Минск — Москва

Второй день в Минске практически весь прошёл вне Минска и был посвящён посещению замков. Во вчерашней записи я рассказал, с каким трудом нам удалось найти человека, который согласился отвезти нас в замки. К счастью, он нас не подвёл и точно в оговорённое время стоял перед гостиницей. По дороге мы несколько раз натыкались на последствия вчерашнего урагана:

Последствия урагана (Минск)

Последствия урагана (Минск)

Но деревьев на дорогах уже не было, поэтому ничего не мешало нам в нашем пути. Первое время я пытался расспрашивать водителя о жизни в Беларуси, но ничего принципиально нового не узнал: многие работают на заводах, но получают мало, некоторые пытаются создавать бизнес, но мало кто добивается успеха, многие возили белорусские товары в Россию, а теперь это стало невыгодно. Короче говоря, есть плюсы, есть минусы, но жить привыкли. Стоило нам немного помолчать, как я сразу же заснул, убаюканный равномерным движением по удивительно ровным белорусским дорогам.

Сколько именно времени мы ехали, я сказать не могу, так как почти всю дорогу проспал, но за время поездки у меня успела сильно затечь шея, так что дорога до первого (Мирского) замка скорее всего заняла больше часа. Замок этот оказался весьма красивым:

Мирский замок (общий вид)

Мирский замок (общий вид)

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

Мирский замок (внутри)

Мирский замок (внутри)

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

Мирский замок (потолок)

Мирский замок (потолок)

Когда мы закончили с осмотром музея и вышли на улицу, нас ждал весьма неприятный сюрприз: снова начался дождь, пока ещё небольшой, но вспышки молний и раскаты грома вдалеке давали понять, что скоро всё станет гораздо хуже. Было принято единственно верное решение — отправится на выставку восковых фигур. Если мне не изменяет память, то на подобной выставке я был ещё в глубоком детстве, поэтому особо не представлял, чего можно ожидать. На деле всё оказалось не так уж  плохо. Понятно, что от провинциального города, коим и является Мир, не стоит ожидать уровня музеев мадам Тюссо, но восковые модели были весьма правдоподобны, а вот интерьеры явно подкачали. Тем не менее мы сделали пару фотографий на фоне постеров фильмов и снова вышли на улицу.

Выставка восковых фигур

Выставка восковых фигур

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

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

Храм в Несвиже

Храм в Несвиже

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

Несвижский замок

Несвижский замок

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

Выставка минералов (Несвижский замок)

Выставка минералов (Несвижский замок)

На обратном пути в Минск я тоже пытался немного поговорить с водителем, но в очередной раз заснул, а проснулся уже непосредственно перед въездом в город. Мы попросили высадить нас в центре, а не везти в гостиницу, потому что в наши планы входило посещение местного ГУМа. От многих людей я слышал много хорошего о качестве белорусских товаров, вот и решил проверить сам. О местном ГУМе я хочу сказать две вещи:

  1. Люди реально ходят в ГУМ, чтобы что-нибудь купить, а не как в Москве, чтобы посмотреть на сумки за четверть миллиона или на часы за полтора;
  2. Сервис там остался на уровне 90-х годов. Т.е. если попросить продавца показать вам какой-нибудь товар, вполне можно услышать в ответ что-нибудь вроде: «Сам что ли взять не можешь»?

Нам, к счастью, повезло вдвойне: во-первых, мы попали ГУМ в день 20% скидок, во-вторых, мне попался крайне редкий вид консультанта — обходительный белорусский консультант гей. В итоге из ГУМа я вышел с новым костюмом, сорочками и гастуками под цвет глаз (если верить этому белорусу, глаза у меня очень красивые). Как-то так прошёл наш второй день в Минске.

День 1. Москва — Минск — Москва

Поезд в Минск приезжал довольно рано, в 6:21 утра. Это всего на 10 минут раньше моего обычного будильника, но я всё равно не выспался. Мне кажется это связано не столько с ранним подъёмом, сколько с тем, что я практически не спал ночью. Обычно люди под стук колёс легко засыпают, но в этот раз что-то пошло не так. То ли пути плохие, то ли поезд, но время от времени нас так сильно трясло, что просыпались почти все.

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

Джакузи

Джакузи

В SPA мы провели около часа, а потом отправились гулять по городу. Можно было бы сразу взять экскурсию на Hop On Hop Off автобусах, но знающие люди сказали, что экскурсия, которую заказывают на вокзале интереснее. Добираясь до вокзала, мы прошли по самому центру города и даже увидели знаменитый минский костёл:

Минский костёл

Минский костёл

В подземном ТЦ, я понял, что Минск это просто город мечта! Купить бутылку воды менее чем за рубль и получить на сдачу 1100 рублей, — это ли не счастье?

До вокзала мы добрались без проблем и сразу же записались на обзорную экскурсию, начинавшуюся  в 14 часов,  а вот с экскурсиями по замкам возникла проблема — мест на них не было ни в одном экскурсионном бюро. Как по мне — это отличная возможность организовать бизнес, но в Беларуси, как я понял, к бизнесу относятся не очень. Тем не менее на автовокзале нам удалось найти мужика, который согласился отвести нас в замки всего за 150 новых белорусских рублей.

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

Экскурсия началась точно по графику в 14:00. Сначала нас привезли в центр города, где мы уже были, во время нашей пешеходной прогулки. Рассказали грустную историю о красном костёле (его построили безутешные родители, потерявшие двух детей). Показали красивую скульптуру архангела Михаила:

Архангел Михаил

Архангел Михаил

И колокол, в основании которого капсулы с землёй из Хиросимы, Нагасаки и Фукусимы. Когда мы шли к памятнику Ленину, у меня начало сильно шуметь в ушах. Я сначала никак не мог понять, чем это вызвано, но вскоре выяснилось, что этот шум слышу не я один. Увидев вдалеке практически чёрное небо, постоянно освещаемое яркими вспышками молний, я понял, что этот шум на самом деле непрекращающиеся раскаты грома. К счастью в автобус мы сели, когда дождь ещё не добрался до центра города. По пути он нас, конечно, нагнал, но пережидать дождь в автобусе гораздо лучше, чем мокнуть под ним. По плану следующая остановка должна была состояться у знаменитой минской библиотеки. Формально остановка была,  даже нашлись люди, которые рискнули выйти в этот ливень. Я к их числу не отношусь, и, если честно, не думаю, что кто-то упрекнёт меня в трусости, скорее похвалят за благоразумие. К сожалению, на фотографии практически невозможно передать силу дождя, но зато легко передать его последствия:

Потоп в Минске

Потоп в Минске

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

Барочный храм в Минске

Барочный храм в Минске

Т.е. красиво, конечно, но без излишеств. Из интересных моментов, хочу отметить музыкальную скамейку:

Музыкальная скамейка

Музыкальная скамейка

Стоит надавить на неё, как начинает играть полонез Огинского «Прощание с Родиной».  Причём интересно в этой скамейке не то, что она играет, стоит только на неё сесть, а то, что она играет именно полонез — единственный известный мне танец-марш.

Наша последняя остановка была на т.н. острове слёз (иногда называют островом Мужества и Скорби) — рукотворном острове, построенным, чтобы служить мемориалом белорусам, воевавшим в Афганистане. Я довольно много путешествовал по европейской части России и видел множество монументов в честь героев войны. Как по мне, они все похожи. Конечно, у каждой семьи, потерявшей сына, брата и/ или мужа своё горе, но вот фантазии и таланта у скульпторов на каждую семью не хватает, поэтому повсюду мы видим вытянутые силуэты женщин со скорбными лицами. На острове скорби такие женщины тоже есть:

Остров скорби

Остров скорби

Но есть там и кое-что интересное, а именно ангел с явными первичными половыми признаками:

Плачущий ангел

Плачущий ангел

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

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

Вход в минское метро

Вход в минское метро

После метро надо было проехать ещё пару остановок на автобусе. Я, наслушавшись рассказов о том, что в Белоруси не бывает пробок, не был готов к такому испытанию. Из-за грозы, пробки всё-таки были, а ещё были десятки белорусов, уставших после работы и яростно желающих попасть домой. Короче в автобусе меня сначала прижали к довольной симпатичной девушке с одной стороны и довольно мерзкому деду с другой, а потом ещё минут двадцать я парился в импровизированной бане. Несмотря на все эти приключения, я всё-таки успел попасть в зоопарк. Даже купил билеты на дополнительную выставку динозавров. Динозавры, понятное дело, были не настоящими, а весьма правдоподобными макетами, по крайней мере нам так рассказали. Посмотреть на них своими глазами мне не удалось, так как ветер повалил пять динозавров, а дождь затопил низину, где эта выставка проходила. Но я ни на секунду не пожалел о том, что сходил в минский зоопарк. Конечно, его не сравнить ни с пражским, ни с венским, ни даже с московским: маленькие вольеры, старые клетки, животные, которых можно увидеть практически в любом зоопарке. Зато весьма неплохой аквариум с пресными и морскими рыбами (вы же помните, как я люблю рыб), отличная экспозиция с пресмыкающимися:

Черепаха (минский зоопарк)

Черепаха (минский зоопарк)

Но самое главное, там есть львы! Не просто клетка на которой написано «лев», а самые настоящие львы, цари зверей:

Лев (минский зоопарк)

Лев (минский зоопарк)