На занятии возник интересный вопрос: как хранится значение столбца по умолчанию и что происходит, если некоторые строки уже существуют при добавлении столбца, а затем значение по умолчанию меняется?
Пример сценария:
- Шаг 1: Создать таблицу с двумя столбцами
- Шаг 2: Добавить 10 строк
- Шаг 3: Добавить третий столбец в таблицу с ненулевым значением по умолчанию
- Шаг 4: Удалить значение по умолчанию для третьего столбца
- Шаг 5: Добавить новое значение по умолчанию для третьего столбца
И при выборке изначальных 10 строк можно продемонстрировать, что третий столбец использует первоначальное значение по умолчанию, установленное на шаге 3. (Не имеет значения, добавлялись ли строки между шагами 3 и 4.)
Это означает, что при добавлении нового столбца должно храниться два значения по умолчанию: одно для уже существующих строк, у которых нет нового столбца, и одно для любых новых строк. Изначально эти два значения будут одинаковыми, но значение для новых строк может измениться (например, на шагах 4 и 5 выше), не затрагивая старые строки. Это работает потому, что после добавления нового столбца (шаг 3 выше) невозможно добавить строки, у которых нет нового столбца.
Именно так всё и работает. Давайте исследуем!
Сначала я создам простую базу данных и тестовую таблицу и вставлю 10 строк. Я использую модель простого восстановления, чтобы очистить журнал с помощью контрольной точки:
CREATE DATABASE [Company];
ALTER DATABASE [Company] SET RECOVERY SIMPLE;
GO
USE [Company];
GO
-- Создадим тестовую таблицу для использования
CREATE TABLE [Test] ([c1] INT IDENTITY, [c2] INT AS ([c1]));
GO
INSERT INTO [Test] DEFAULT VALUES;
GO 10
SELECT * FROM [Test];
GO
c1 c2
----------- -----------
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
10 10
Теперь я очищу журнал, добавлю третий столбец со значением по умолчанию и посмотрю, в какие системные таблицы были добавлены записи из-за этого изменения:
CHECKPOINT;
GO
-- Добавим столбец со значением по умолчанию
ALTER TABLE [Test] ADD [c3] CHAR (6) NOT NULL CONSTRAINT [OriginalDefault] DEFAULT 'BEFORE';
GO
SELECT [AllocUnitName] FROM fn_dblog (NULL, NULL)
WHERE [Operation] = 'LOP_INSERT_ROWS';
GO
AllocUnitName
-----------------------
sys.syscolpars.clst
sys.syscolpars.nc
sys.sysrscols.clst
sys.sysseobjvalues.clst
sys.sysschobjs.clst
sys.sysschobjs.nc1
sys.sysschobjs.nc2
sys.sysschobjs.nc3
sys.sysobjvalues.clst
Отлично. Эти системные таблицы выполняют следующие функции:
sys.syscolpars: определения столбцов таблицы (реляционные метаданные)sys.sysrscols: определения столбцов набора строк (метаданные подсистемы хранения — информация для интерпретации структур записей на страницах)sys.sysseobjvalues: различные значения подсистемы хранения разного назначенияsys.sysschobjs: реляционные объекты (например, таблицы, ограничения)sys.sysobjvalues: различные реляционные значения разного назначения
Я посмотрю в этих таблицах, как вставленные строки связаны с нашей таблицей. Сначала мне понадобится несколько идентификаторов (используя для этого мою удобную процедуру):
EXEC sp_allocationmetadata N'Test';
GO
Object Name Index ID Partition ID Alloc Unit ID Alloc Unit Type First Page Root Page First IAM Page
----------- -------- ----------------- ----------------- --------------- ---------- --------- --------------
Test 0 72057594040549376 72057594045792256 IN_ROW_DATA (1:247) (0:0) (1:288)
И теперь я могу запросить эти системные таблицы. Обратите внимание, что это «скрытые» системные таблицы, поэтому вы не можете их запрашивать, если не подключитесь с помощью выделенного административного соединения (DAC). Самый простой способ сделать это — добавить префикс «admin:» к строке подключения SSMS (и если вы подключаетесь к удалённому серверу, на сервере должна быть включена опция настройки sp_configure remote admin connections). Убедитесь, что после подключения вы используете правильную базу данных, так как DAC помещает вас в базу master.
SELECT * FROM sys.syscolpars WHERE [id] = OBJECT_ID (N'Test');
GO
id number colid name xtype utype length prec scale collationid status maxinrow xmlns dflt chk idtval
--------- ------ ----- ---- ----- ----- ------ ---- ----- ----------- ------ -------- ----- ----------- --- ---------
245575913 0 1 c1 56 56 4 10 0 0 5 4 0 0 0 0x0A000000010000000100000000
245575913 0 2 c2 56 56 4 10 0 0 209 4 0 0 0 NULL
245575913 0 3 c3 175 175 6 0 0 872468488 3 6 0 261575970 0 NULL
Это реляционные определения столбцов в таблице, и вы можете видеть, что у c3 указано ограничение по умолчанию с идентификатором 261575970.
SELECT * FROM sys.sysschobjs WHERE [id] = 261575970;
GO
id name nsid nsclass status type pid pclass intprop created modified status2
----------- --------------- ---- ------- ------ ---- --------- ------ ------- ----------------------- ----------------------- -------
261575970 OriginalDefault 1 0 131072 D 245575913 1 3 2017-04-26 13:37:42.463 2017-04-26 13:37:42.463 0
Это ограничение с именем OriginalDefault, типа D (default — значение по умолчанию), и само значение по умолчанию имеет идентификатор 245575913.
SELECT * FROM sys.sysobjvalues WHERE [objid] = 261575970;
GO
valclass objid subobjid valnum value imageval
-------- --------- -------- ------ ----- ----------------------
1 261575970 0 0 2 0x28274245464F52452729
И столбец imageval содержит значение по умолчанию в виде шестнадцатерично-закодированных ASCII-значений. Используя таблицу ASCII на Wikipedia, значение равно («BEFORE»), включая круглые скобки.
Итак, это значение по умолчанию для новых строк. А что насчёт значения по умолчанию для уже существующих строк?
SELECT * FROM sys.sysrscols WHERE [rsid] = 72057594040549376;
GO
rsid rscolid hbcolid rcmodified ti cid ordkey maxinrowlen status offset nullbit bitpos colguid
----------------- ------- ------- ---------- ---- --------- ------ ----------- ------ ------ ------- ------ -------
72057594040549376 1 1 10 56 0 0 0 128 4 1 0 NULL
72057594040549376 2 2 10 56 0 0 0 128 8 2 0 NULL
72057594040549376 3 3 0 1711 872468488 0 0 640 12 3 0 NULL
Это определения столбцов таблицы на уровне подсистемы хранения. Значение status указывает, что значение может отсутствовать в строке, и откуда его нужно брать по умолчанию.
SELECT * FROM sys.sysseobjvalues WHERE [id] = 72057594040549376;
GO
valclass id subid valnum value imageval
-------- ----------------- ----- ------ ------ --------
1 72057594040549376 3 0 BEFORE NULL
И вот он — механизм хранения значения по умолчанию в подсистеме хранения для столбца c3 для тех строк, которые существовали до добавления c3.
Теперь я создам контрольную точку, удалю ограничение по умолчанию и посмотрю, что произошло в журнале:
CHECKPOINT;
GO
ALTER TABLE [Test] DROP CONSTRAINT [OriginalDefault];
GO
SELECT [AllocUnitName] FROM fn_dblog (NULL, NULL)
WHERE [Operation] = 'LOP_DELETE_ROWS';
GO
AllocUnitName
---------------------
sys.sysobjvalues.clst
sys.sysschobjs.nc1
sys.sysschobjs.nc2
sys.sysschobjs.nc3
sys.sysschobjs.clst
Итак, реляционное значение по умолчанию удаляется в обратном порядке по сравнению с тем, как оно добавлялось. Обратите внимание, что значение по умолчанию в подсистеме хранения не было удалено.
Теперь я создам новое ограничение по умолчанию для столбца c3:
CHECKPOINT;
GO
ALTER TABLE [Test] ADD CONSTRAINT [NewDefault] DEFAULT 'AFTER' FOR [c3];
GO
SELECT [AllocUnitName] FROM fn_dblog (NULL, NULL)
WHERE [Operation] = 'LOP_INSERT_ROWS';
GO
AllocUnitName
---------------------
sys.sysschobjs.clst
sys.sysschobjs.nc1
sys.sysschobjs.nc2
sys.sysschobjs.nc3
sys.sysobjvalues.clst
И повторное выполнение различных запросов приводит нас к новому реляционному значению по умолчанию для столбца («AFTER»), включая круглые скобки.
Итак, просто чтобы доказать то, что я сказал до начала исследования, я добавлю десять новых строк, которые будут иметь значение c3 равное AFTER, а затем выполню запрос к таблице, и я увижу, что изначальным десяти строкам, у которых нет c3, будет присвоено исходное значение по умолчанию BEFORE:
INSERT INTO [Test] DEFAULT VALUES;
GO 10
SELECT * FROM [Test];
GO
c1 c2 c3
----------- ----------- ------
1 1 BEFORE
2 2 BEFORE
3 3 BEFORE
4 4 BEFORE
5 5 BEFORE
6 6 BEFORE
7 7 BEFORE
8 8 BEFORE
9 9 BEFORE
10 10 BEFORE
11 11 AFTER
12 12 AFTER
13 13 AFTER
14 14 AFTER
15 15 AFTER
16 16 AFTER
17 17 AFTER
18 18 AFTER
19 19 AFTER
20 20 AFTER
Надеюсь, вы нашли это интересным! (И не забудьте разорвать соединение DAC).

Комментариев нет:
Отправить комментарий