Здесь перечислены общие рекомендации по избежанию наиболее популярных граблей, по которым так любят ходить разработчики баз данных. Хочу лишь отметить один факт нарушение перечисленных требований приводит к резкому увеличению времени разработки, росту количества ошибок, неоправданным сложностям в сопровождении и в конечном итоге, к провалу всего проекта.
Не парьте мне мозги.
Одной сущности должно соответствовать всегда одно имя это правило, в котором не может быть исключений.
Написание имен должно быть максимально стандартизировано.
Регистр букв также важен, даже если средство разработки его не учитывает
(зато регистр хорошо учитывается людьми, хотя многие об этом и не подозревают).
Лучше всего использовать слова английского языка, без сокращений.
Большинство средства разработки не поддерживает идентификаторы,
состоящие из букв других языков и правильно делает.
Немногочисленные стандартные аббревиатуры и сокращения
должны быть описаны в специальном словаре.
Пример: Если типу объекта соответствует имя ObjectType, то в обращении не должно быть имен вроде ObjType, Objecttype или OBJ_TYPE.
Использование стандартных имен позволяет сэкономить кучу времени, необходимого на поиск нужного имени, а также избежать возможных недоразумений.
Что это за хрень?
Часто неопытные разработчики используют какие-нибудь поля для хранения значений вроде 0, 1, 2 и т.п. Эти поля не ссылаются на другую таблицу, зато используются где-нибудь в выражениях:
if State = 2 then ... else ...
Все ведь понятно, не правда ли?
Немногим лучше выглядит и такой вариант:
«U», «T», «S».
Кто угадает, что здесь означает «U»?
Правильным решением будет создание отдельной таблицы (Id, Code, Name, Note),
содержащей набор возможных значений для этого поля.
В поле же надо хранить идентификатор (Id) значения из этой таблицы,
при необходимости используя код и наименование значения.
Отсутствие загадочных значений освобождает память разработчика от ненужного мусора и уменьшает вероятность возникновения ошибок и непонимания.
Не создавай граблей на пустом месте.
Таблицы и поля стоит создавать с достаточно большим запасом прочности, чтобы не было потом мучительно больно.
Если в задании написано, что какой-нибудь код содержит 17 символов, совсем необязательно понимать это буквально и объявлять поле как char(17). Ведь завтра требования могут измениться, и уже понадобится 22 символа или больше. Гораздо лучше выглядит varchar(120), а места оно занимает не больше.
Когда создается таблица с предполагаемым числом записей
не больше нескольких тысяч,
вполне разумным кажется использование smallint (2-хбайтовое целое) поля
в качестве первичного ключа.
А что же будет, если количество записей все-таки превысит 32 тысячи?
Придется менять первичный ключ в этой таблице и все внешние ключи,
ссылающиеся на нее.
Этого можно было бы избежать,
сразу сделав первичный ключ размеров в 4 байта.
Кажется, что всегда можно легко произвести
любые необходимые изменения в структуре базы данных,
однако это далеко не так.
База может находиться в круглосуточной работе, в репликации,
может быть уже заполненной многомиллионными данными и т.п.
И экономия на байтах здесь не уместна.
Отсутствие мелочных ограничений позволяет разработчику не думать о них и создавать более устойчивые и гибкие структуры.
Надо быть проще.
В качестве первичных ключей для всех таблиц сущностей лучше всего использовать суррогатные ключи достаточно большого размера (416 байт). При этом, все такие ключи должны быть одного типа.
Благодаря этому, многие проблемы уходят
и никогда не возвращаются (разве что в кошмарах).
Так как ключ суррогатный,
то отпадает необходимость поиска и использования
естественного ключа (часто составного).
Из-за большого размера ключа не возникает
ситуация его переполнения.
Наличие у всех сущностей идентификатора одного типа
позволяет организовать стандартную работу с этими сущностями.
Единственной проблемой может стать наличие двусторонней репликации между базами данных. Но это легко решается использованием глобального идентификатора в качестве ключа либо разделением диапазонов выделенных для ключей в разных БД.
Одинаковые ключи дают возможность работать с разными сущностями одинаковыми методами, часто используя один и тот же код.
Надо быть гибче.
Нормализация и еще раз нормализация вот залог жизнеспособности любой базы данных.
Как учат классики, все отдельные сущности должны быть выделены
в отдельные таблицы.
При этом связь с ними осуществляется по первичному ключу.
Одинаковые сущности, наоборот, сводятся в одну таблицу.
Зависимости между разными сущностями должны быть минимальны.
Из-за несовершенства SQL (и особенно его реализаций), полностью нормализованная БД может оказаться плохо приспособленной к получению сложных запросов. Поэтому, в исключительных случаях, допускается некоторое дублирование информации для ускорения выборок.
Независимость отдельных сущностей может упростить проект и добавить ему гибкости.
Долой Copy&Paste!
Казалось бы, тривиальное правило, гласящее, что повторяющийся код надо выносить в отдельные функции, знает даже самый неграмотный программист. Однако в случае с SQL все не так просто.
Дело в том, что SQL изначально не предназначен для создания функций, процедур, триггеров и, соответственно, не предполагает для них никакого стандарта. Поэтому у всех SQL-серверов есть свои собственные расширения для написания бизнес-логики. Эти расширения не совместимы друг с другом, а их уровень варьируется от достаточно высокого до совершенно убогого (лишь бы было).
Иногда даже опытные разработчики,
впервые столкнувшись с неадекватными SQL-расширениями,
впадают в какой-то ступор и забывают все свои навыки,
пытаясь использовать навязанные им приемы работы.
При этом, часто пишут кучу однообразных триггеров и процедур,
выполняющих одинаковые действия для разных таблиц.
Тем не менее, правило повторного использования кода никто еще не отменял,
и обычно его везде можно применять, хотя и не всегда очевидным образом
(например, передавая имя таблицы в качестве параметра).
Повторное использование улучшает качество кода и рекомендуется к применению везде, даже если средство разработки этого не поощряет.
Invalid programmer is detected. Abort, Remove, Must Die?
Такое же стандартное правило, как и предыдущее если хочешь, чтобы программа делала то, что нужно, необходимо проверять результат выполнения каждой функции (и каждого SQL-оператора).
В клиентском приложении должны везде проверяться
коды ошибок вызываемых функций.
При возникновении ошибки, ее стоит записать в лог-файл,
а пользователю должно быть показано уже
обработанное внятное сообщение на человеческом языке.
То есть вместо «Network link failur»
надо вывести
«У вас сеть отвалилась, дальнейшая работа невозможна».
Если в приложении все более-менее понятно,
то при написании процедур на SQL-сервере разработчики, видимо, надеются,
что сервер сам прекратит выполнение процедуры при возникновении ошибки.
Как бы не так.
После каждого SQL-оператора обязательно
должен проверяться и обрабатываться код последней ошибки.
Правильность работы программы не может быть обеспечена без тотальной обработки ошибок.
Скажи блокировкам НЕТ!
Большинство SQL-серверов используют технологию блокировок при любой модификации данных. То есть, данные, измененные в транзакции одним из пользователей, недоступны для других пользователей до завершения этой транзакции.
Совсем избежать блокировок нельзя,
однако можно уменьшить продолжительность транзакций.
Для этого перед выполнением транзакции
для нее должны быть уже подготовлены все необходимые данные.
Внутри транзакции не должно быть никаких обращений
к внешним устройствам (файлам, сети и т.п.)
только к базе данных.
Ситуация, когда внутри транзакции происходит диалог с пользователем,
тем более недопустима.
Короткие транзакции дают возможность большему числу пользователей одновременно работать с базой данных, не конфликтуя друг с другом.
Кому нужна база без головы?
Метаданные описывают структуру базы данных и взаимосвязи ее объектов.
При некоторых манипуляциях с базой данных
может потребоваться информация о том,
какие сущности хранятся в этой базе, в каких таблицах и полях,
как они связаны друг с другом.
Частично эту информацию можно получить
из системного каталога SQL-сервера.
Но системный каталог предназначен скорее не для разработчика,
а для внутреннего использования самим сервером,
поэтому найти там что-либо весьма проблематично.
При этом, его структура может меняться даже в разных версиях одного сервера,
не говоря уже об отсутствии какой-либо совместимости с другими SQL-серверами.
Даже довольно неполные метаданные имеют широкую область применения. Но чаще всего они используются для упрощения собственной работы программистов, вплоть до автоматической генерации приложения и объектов БД.
Наличие метаданных дает полную информацию о базе данных, облегчая разработчикам решение многих задач.
Кто владеет прошлым тот владеет будущим.
Большинство объектов, хранящихся в базе данных, могут меняться с течением времени.
К сожалению, почти все современные СУБД не хранят историю изменений и не позволяют получить состояние объектов на определенную дату. Об этом должен позаботиться разработчик базы данных.
Только база данных, которая хранит всю историю изменений всех своих объектов, может претендовать на полноту и завершенность. Иначе простейший запрос, вроде «Дайте список объектов в том виде, в котором они были на 01.04.1997 г.», так и останется без ответа.
Любая база данных, хранящая изменяемые объекты, должна содержать полную историю всех их изменений.