Темпоральные таблицы SQL Server: практические рекомендации
Таблицы, которые возвращают значение данных в таблице в определенный момент времени, были у нас с момента появления первой реляционной базы данных, но всегда требовали специальных запросов и ограничений и могут быть непростыми для получения правильной информации. Системно управляемые версии темпоральных таблиц, впервые появившиеся в SQL Server 2016, заставляют такие таблицы вести себя как любые другие. Как создать или изменить существующую таблицу? Как получить таблицу OLTP, оптимизированную для памяти, как темпоральную?
Темпоральные таблицы или таблицы с системным управлением версиями были введены как функция базы данных в SQL Server 2016. Это дает нам тип таблицы, которая может предоставлять информацию о данных, которые хранились в любое указанное время, а не только текущие данные. ANSI SQL 2011 сначала определил темпоральную таблицу как функцию базы данных, и теперь она поддерживается в SQL Server.
Наиболее распространенные бизнес-применения для темпоральных таблиц:
- Медленно меняющиеся измерения. Темпоральные таблицы предоставляют более простой способ запрашивать данные, которые являются текущими в течение определенного периода времени, такие как данные с разделением по времени, что является хорошо известной проблемой в базах данных хранилищ данных.
- Аудит данных. Темпоральные таблицы предоставляют контрольный журнал для определения того, когда данные были изменены в «родительской» таблице. Это помогает удовлетворить требования нормативного соответствия и проводить экспертизу данных, когда это необходимо, путем отслеживания и аудита изменений данных с течением времени.
- Исправление или восстановление повреждений на уровне записи. Создание способа «отмены» изменения данных в строке таблицы без простоя в случае случайного удаления или обновления записи. Таким образом, предыдущая версия данных может быть извлечена из таблицы истории и вставлена обратно в «родительскую» таблицу. - Это помогает, когда кто-то (или из-за некоторых ошибок приложения) случайно удаляет данные, и вы хотите вернуться к ним или восстановить их.
- Воспроизведение финансовых отчетов, счетов и выписок с правильными данными на дату выдачи документа. Темпоральные таблицы позволяют запрашивать данные, как это было в конкретный момент времени, чтобы проверить состояние данных, как это было тогда.
- Анализ тенденций путем понимания того, как данные изменяются с течением времени в ходе текущей бизнес-деятельности, и для расчета тенденций изменения данных с течением времени.
В давние времена до появления SQL Server 2016 механизм регистрации данных должен был быть явно установлен в триггере. Чтобы привести простой пример, нам нужно автоматизировать ведение истории для таблицы Department со следующей структурой, начиная с самой таблицы Department:
CREATE TABLE dbo.Department ( DeptID INT NOT NULL, DeptName VARCHAR(50) NOT NULL, ManagerID INT NULL, ParentDeptID INT NULL, Created DATETIME NOT NULL CONSTRAINT DF_Department_Created DEFAULT GETDATE(), CONSTRAINT PK_Department_DeptID PRIMARY KEY CLUSTERED(DeptID ASC) ON [PRIMARY] ) ON [PRIMARY]; GO |
Следующим шагом является создание таблицы Department_Log с двумя дополнительными столбцами, которые предоставляют историю изменений
- LogDate
- LogAction
CREATE TABLE dbo.Department_Log ( DeptID INT NOT NULL, DeptName VARCHAR(50) NOT NULL, ManagerID INT NULL, ParentDeptID INT NULL, Created DATETIME NOT NULL, LogDate DATETIME NOT NULL, LogAction VARCHAR(10) NOT NULL ) ON [PRIMARY]; GO |
Когда таблица «истории» будет готова, мы можем создать триггер для регистрации изменений действий UPDATE и DELETE:
CREATE TRIGGER dbo.tr_Department_Log ON dbo.Department FOR UPDATE, DELETE AS BEGIN SET NOCOUNT ON; IF (SELECT COUNT(1) FROM inserted JOIN deleted ON Inserted.DeptID = Deleted.DeptID ) > 0 BEGIN INSERT dbo.Department_Log (DeptID, DeptName, ManagerID, ParentDeptID, Created, LogDate, LogAction) SELECT Deleted.DeptID, Deleted.DeptName, Deleted.ManagerID, Deleted.ParentDeptID, Deleted.Created, GETDATE(), 'UPDATED' FROM deleted; END; ELSE BEGIN INSERT dbo.Department_Log (DeptID, DeptName, ManagerID, ParentDeptID, Created, LogDate, LogAction) SELECT Deleted.DeptID, Deleted.DeptName, Deleted.ManagerID, Deleted.ParentDeptID, Deleted.Created, GETDATE(), 'DELETED' FROM deleted; END; SET NOCOUNT OFF; END; GO |
Чтобы продемонстрировать, как таблица Department_Log работает с триггером, мы трижды обновляли строку, в которой DeptID = 1, затем удаляли эту строку и, наконец, при последнем обновлении для столбца DeptName устанавливалось первоначальное значение.
update dbo.Department SET DeptName = '' where DeptID = 1 update dbo.Department SET DeptName = 'Engineering IT' where DeptID = 1 update dbo.Department SET DeptName = 'Engineering WEB' where DeptID = 1 DELETE dbo.Department where DeptID = 1 INSERT dbo.Department(DeptID, DeptName) SELECT DeptID,DeptName FROM Department_Log WHERE DeptID = 1 and LogAction = 'DELETED' update dbo.Department SET DeptName = 'Engineering' where DeptID = 1 select DeptID, DeptName,Created,LogDate,LogAction from Department_Log |
Результат из таблицы Department_Log показан на следующем рисунке:
Функция темпоральных таблиц в SQL Server 2016 может значительно упростить механизм ведения журналов. В этой статье приводятся пошаговые инструкции по созданию таблиц с системной версией.
Чтобы перенести таблицу в темпоральную таблицу, для существующей таблицы можно установить параметр темпоральной таблицы. Чтобы создать новую темпоральную таблицу, вам просто нужно установить для параметра темпоральной таблицы значение ON (например, SYSTEM_VERSIONING = ON). Когда опция темпоральной таблицы включена, SQL Server 2016 автоматически генерирует «историческую» таблицу и поддерживает как родительские, так и исторические таблицы, одну для хранения фактических данных, а другую для исторических данных. Столбцы периода темпоральной таблицы SYSTEM_TIME (например, SysStartTime и SysEndTime) позволяют механизму запрашивать данные для другого временного интервала более эффективно. Обновленные или удаленные данные перемещаются в «историческую» таблицу, в то время как «родительская» таблица содержит последнюю версию строки для обновленных записей.
В чем подвох?
Наиболее важные соображения, условия и ограничения темпоральных таблиц:
- Чтобы связать записи между темпоральной таблицей и таблицей истории, у вас должен быть первичный ключ в темпоральной таблице. Однако таблица истории не может иметь первичный ключ.
- Тип данных datetime2 должен быть установлен для столбцов периода SYSTEM_TIME (например, SysStartTime и SysEndTime).
- Когда вы создаете таблицу истории, вы всегда должны указывать и схему, и имя таблицы темпоральной таблицы в таблице истории.
- Сжатие PAGE является настройкой по умолчанию для таблицы истории.
- Темпоральные таблицы поддерживают типы данных BLOB-объектов (nvarchar (макс.), Varchar (макс.), Varbinary (макс.), Ntext, текст и изображение), которые могут влиять на стоимость хранения и иметь проблемы с производительностью.
- Темпоральные и хронологические таблицы должны быть созданы в одной базе данных. Вы не можете использовать связанный сервер для предоставления темпоральных таблиц.
- Вы не можете использовать ограничения, первичный ключ, внешние ключи или ограничения столбцов для таблиц истории.
- Вы не можете ссылаться на временные таблицы в индексированных представлениях, в которых есть запросы, использующие предложение FOR SYSTEM_TIME
- На столбцы периода SYSTEM_TIME нельзя напрямую ссылаться в инструкциях INSERT и UPDATE.
- Вы не можете использовать TRUNCATE TABLE, когда SYSTEM_VERSIONING включен.
- Вы не можете напрямую изменять данные в таблице истории.
Создание темпоральной таблицы
Мы показали, как создавать темпоральные и хронологические таблицы в одном сценарии DDL в листинге 1. Как мы упоминали ранее, столбцы SysStartTime и SysEndTime с типом данных datetime2 для обоих столбцов требуются для временной таблицы. Столбец SysStartTime должен быть GENERATED ALWAYS AS ROW START NOT NULL, а SysEndTime должен быть GENERATED ALWAYS AS ROW END NOT NULL. Вы не обязаны указывать значения по умолчанию для этих столбцов, но мы бы порекомендовали это. И столбцы SysStartTime, и SysEndTime должны быть указаны в столбце PERIOD FOR SYSTEM_TIME (как MSDN определил PERIOD, в других публикациях условие вызовов PERIOD).
Примечание. Системные версии столбцов не обязательно должны называться как SysStartTime и SysEndTime, но имена столбцов следует выбирать так, чтобы они отражали функцию захвата времени. Параметры GENERATED ALWAYS AS ROW START/END и PERIOD FOR SYSTEM_TIME (nameFrom, nameTo), включают функцию темпоральной таблицы.
CREATE TABLE Department ( DeptID INT NOT NULL PRIMARY KEY CLUSTERED, DeptName VARCHAR(50) NOT NULL, ManagerID INT NULL, ParentDeptID INT NULL, SysStartTime DATETIME2 GENERATED ALWAYS AS ROW START CONSTRAINT DF_Department_SysStartTime DEFAULT SYSUTCDATETIME() NOT NULL, SysEndTime DATETIME2 GENERATED ALWAYS AS ROW END CONSTRAINT DF_Department_SysEndTime DEFAULT CONVERT( DATETIME2, '9999-12-31 23:59:59' ) NOT NULL, PERIOD FOR SYSTEM_TIME(SysStartTime, SysEndTime) ) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.DepartmentHistory)); |
Листинг 1: Создание темпоральных и исторических таблиц
После создания темпоральной таблицы подчеркнутая таблица истории создается автоматически (рисунок 1), а для истории будет создан CLUSTERED INDEX с обоими столбцами SysStartTime и SysEndTime (или именем, выбранным для определения версии системы). таблица, листинг 2.
CREATE CLUSTERED INDEX ix_DepartmentHistory ON dbo.DepartmentHistory (SysStartTime ASC, SysEndTime ASC ) ON [PRIMARY]; |
Листинг 2: Создание кластерного индекса
Если новый столбец должен быть добавлен в темпоральную таблицу, то необходимо разрешить ALTER TABLE… ADD столбец DDL, и новый столбец будет автоматически отражен в таблице истории.
Рисунок 1: Отображение вновь созданных временных и исторических таблиц в Object Explorer.
Однако невозможно использовать DROP TABLE DDL для темпоральной таблицы. Во-первых, SYSTEM_VERSIONING должен быть выключен.
ALTER TABLE Department SET (SYSTEM_VERSIONING = OFF); |
Листинг 3: Отключение SYSTEM_VERSIONING для таблицы Department.
Когда для SYSTEM_VERSIONING установлено значение OFF, временные и хронологические таблицы становятся обычными таблицами. Команда DROP TABLE может затем использоваться для этих таблиц.
CREATE TABLE Department_Exist ( DeptID int NOT NULL PRIMARY KEY CLUSTERED , DeptName varchar(50) NOT NULL , ManagerID INT NULL , ParentDeptID int NULL ) |
Листинг 4: Создание таблицы Department_Exist.
ALTER TABLE dbo.Department_Exist ADD SysStartTime datetime2 GENERATED ALWAYS AS ROW START CONSTRAINT DF_Department_Exist_SysStartTime DEFAULT SYSUTCDATETIME() NOT NULL, SysEndTime datetime2 GENERATED ALWAYS AS ROW END CONSTRAINT DF_Department_Exist_SysEndTime DEFAULT CONVERT (DATETIME2, '9999-12-31 23:59:59') NOT NULL, PERIOD FOR SYSTEM_TIME (SysStartTime, SysEndTime) GO
ALTER TABLE dbo.Department_Exist SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Department_ExistHistory)) GO |
Листинг 5: Добавление системных версий столбцов и включение системных версий в таблице Department_Exist.
Существующая таблица Преобразованная в темпоральную таблица
Рисунок 2: Сравнение параллельного Department_Exist после преобразования я в темпоральную таблицу
Проверьте метаданные темпоральных таблиц:
-- List temporal tables, temporal_type = 2 SELECT tables.object_id, temporal_type, temporal_type_desc, history_table_id, tables.name FROM sys.tables WHERE temporal_type = 2 -- SYSTEM_VERSIONED_TEMPORAL_TABLE -- List temporal tables and history tables SELECT h.name temporal_name, h.temporal_type_desc, h.temporal_type, t.name AS history_table_name, t.temporal_type, t.temporal_type_desc FROM sys.tables t JOIN sys.tables h ON t.object_id = h.history_table_id |
Преобразование оптимизированной в памяти таблицы OLTP в таблицу с системным управлением версиями
Хотя процесс преобразования оптимизированной в памяти таблицы OLTP в таблицу с системным управлением версиями аналогичен, есть некоторые различия, которые мы должны рассмотреть и продемонстрировать в этом разделе.
При преобразовании таблицы, оптимизированной для памяти, в таблицу с системным управлением версиями необходимо знать некоторые конкретные детали:
- Оптимизированные в памяти таблицы должны быть долговечными (DURABILITY = SCHEMA_AND_DATA).
- Оптимизированная таблица истории в памяти создается на основе дисков.
- Запросы, которые влияют только на родительскую таблицу, могут использоваться в компиляционных модулях T-SQL. Вы не можете использовать временные запросы, используя предложение FOR SYSTEM TIME в изначально скомпилированных модулях, но можно использовать предложение FOR SYSTEM TIME с оптимизированными в памяти таблицами в специальных запросах и не собственных модулях.
- Внутренняя оптимизированная промежуточная таблица в памяти создается автоматически, чтобы принимать самые последние изменения (INSERT, DELETE) при изменениях в оптимизированной родительской таблице в памяти, когда SYSTEM_VERSIONING = ON.
- Данные из внутренней промежуточной таблицы, оптимизированной для памяти, регулярно перемещаются в таблицу хронологии на основе диска с помощью задачи асинхронной очистки данных. Этот механизм очистки данных имеет целью сохранить на внутренних буферах памяти менее 10% потребления памяти их родительскими объектами. DMV sys.dm_db_xtp_memory_consumers поможет отследить общее потребление памяти.
- Сброс данных может быть вызван путем вызова хранимой процедуры sys.sp_xtp_flush_temporal_history @schema_name, @object_name.
- Когда SYSTEM_VERSIONING = OFF или когда схема таблицы версии системы изменяется путем добавления, удаления или изменения столбцов, все содержимое внутреннего промежуточного буфера перемещается в таблицу истории на основе диска.
- Запрос исторических данных фактически выполняется на уровне изоляции SNAPSHOT и всегда возвращает объединение между промежуточным буфером в памяти и таблицей на диске без дубликатов.
- Операции ALTER TABLE, которые изменяют схему таблицы внутри, должны выполнять сброс данных, что может замедлить операцию.
Создание нового оптимизированного в памяти OLTP с включенной опцией System-Versioned Table
DDL для создания новой оптимизированной в памяти таблицы с параметрами темпоральной таблицы очень близок по своему синтаксису к традиционной дисковой таблице. Синтаксис оптимизированной таблицы в памяти имеет блок WITH для первоначальной установки свойств MEMORY_OPTIMIZED и DURABILITY. Поэтому необходимо добавить свойство SYSTEM_VERSIONING через запятую, как показано в листинге 7.
CREATE TABLE dbo.InMemory ( UniqueName varchar(50) NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 131072), City varchar(32) NULL, State_Province varchar(32) NULL, LastModified datetime NOT NULL , SysStartTime datetime2 GENERATED ALWAYS AS ROW START CONSTRAINT DF_InMemory_SysStartTime DEFAULT GETDATE() NOT NULL , SysEndTime datetime2 GENERATED ALWAYS AS ROW END CONSTRAINT DF_InMemory_SysEndTime DEFAULT CONVERT (DATETIME2, '9999-12-31 23:59:59') NOT NULL , PERIOD FOR SYSTEM_TIME (SysStartTime,SysEndTime) ) WITH ( MEMORY_OPTIMIZED = ON , DURABILITY = SCHEMA_AND_DATA, SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.InMemory_History) ) |
Листинг 7: Создание нового оптимизированного в памяти OLTP с включенной опцией System-Versioned Table
Добавление опции System-Versioned Table в существующую оптимизированную в памяти таблицу OLTP.
Как показано в листинге 8, сложнее преобразовать существующую оптимизированную в памяти OLTP-таблицу в таблицу с системным управлением версиями.
Чтобы продемонстрировать этот механизм, давайте создадим таблицу:
CREATE TABLE dbo.InMemoryExist ( UniqueName varchar(50) NOT NULL PRIMARY KEY NONCLUSTERED HASH WITH (BUCKET_COUNT = 131072), City varchar(32) NULL, State_Province varchar(32) NULL ) WITH ( MEMORY_OPTIMIZED = ON , DURABILITY = SCHEMA_AND_DATA ) |
Листинг 8: Создание новой оптимизированной в памяти таблицы OLTP
Когда таблица создана, нам нужно добавить параметры темпоральной таблицы, прежде чем какие-либо данные будут добавлены в таблицу, как показано в листинге 9:
ALTER TABLE dbo.InMemoryExist ADD SysStartTime datetime2 GENERATED ALWAYS AS ROW START CONSTRAINT DF_InMemoryExist_SysStartTime DEFAULT SYSUTCDATETIME() NOT NULL, SysEndTime datetime2 GENERATED ALWAYS AS ROW END CONSTRAINT DF_InMemoryExist_SysEndTime DEFAULT CONVERT (DATETIME2, '9999-12-31 23:59:59') NOT NULL, PERIOD FOR SYSTEM_TIME (SysStartTime, SysEndTime) GO
ALTER TABLE dbo.InMemoryExist SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.InMemoryExist_History)) GO |
Листинг 9: Добавление параметров темпоральной таблицы
Если таблица уже содержит данные, то процесс преобразования таблицы в таблицу с системным управлением версиями является более сложным.
Если InMemoryExist был создан с опцией системного управления версиями, нам нужно удалить таблицы InMemoryExist и InMemoryExist_History:
ALTER TABLE InMemoryExist set (SYSTEM_VERSIONING = OFF) GO DROP TABLE InMemoryExist GO DROP TABLE InMemoryExist_History GO |
Мы воссоздаем таблицу (используйте пример кода, приведенный в листинге 8, приведенном выше в этом разделе). Затем мы вставляем данные в таблицу InMemoryExist:
INSERT InMemoryExist select NEWID(),name, type_desc from sys.objects where is_ms_shipped = 0 |
При запуске кода, чтобы добавить временные параметры таблицы, листинг 9, будут отброшены следующие ошибки:
Msg 13575, Level 16, State 0, Line 21 ADD PERIOD FOR SYSTEM_TIME failed because table 'database.dbo.InMemoryExist' contains records where end of period is not equal to MAX datetime. Msg 13510, Level 16, State 1, Line 29 |
Невозможно установить для SYSTEM_VERSIONING значение ON, если период SYSTEM_TIME не определен.
Чтобы избежать этих ошибок, нам нужно добавить опции контроля версий системы в более подробных шагах:
--Step 1. Adding nullable the columns ALTER TABLE dbo.InMemoryExist ADD SysStartTime datetime2 NULL GO ALTER TABLE dbo.InMemoryExist ADD SysEndTime datetime2 NULL GO --Step 2 Adding the default constraints ALTER TABLE InMemoryExist ADD CONSTRAINT DF_InMemoryExist_SysStartTime DEFAULT GETDATE() FOR SysStartTime; GO ALTER TABLE InMemoryExist ADD CONSTRAINT DF_InMemoryExist_SysEndTime DEFAULT CAST('9999-12-31 23:59:59.9999999' AS DATETIME2) FOR SysEndTime; --Step 3 Updating the column UPDATE dbo.InMemoryExist SET SysStartTime = '19000101 00:00:00.0000000' ,SysEndTime = '99991231 23:59:59.9999999' GO --Step 4 Setting NOT NULL to the columns ALTER TABLE dbo.InMemoryExist ALTER COLUMN SysStartTime datetime2 NOT NULL GO ALTER TABLE dbo.InMemoryExist ALTER COLUMN SysEndTime datetime2 NOT NULL GO --Step 5 Adding PERIOD FOR SYSTEM_TIME option ALTER TABLE InMemoryExist ADD PERIOD FOR SYSTEM_TIME (SysStartTime, SysEndTime) GO --Step 6 Setting SYSTEM_VERSIONING property ALTER TABLE InMemoryExist SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.InMemoryExist_History)) GO |
Теперь таблица InMemoryExist включена для процессов контроля версий системы.
Указание свойства DATA_CONSISTENCY_CHECK
Вы должны установить SYSTEM_VERSIONING с DATA_CONSISTENCY_CHECK = ON, чтобы обеспечить проверку целостности данных для существующих данных. Однако свойство DATA_CONSISTENCY_CHECK в настоящее время имеет профиль утечки памяти при использовании. Если вы решили включить DATA_CONSISTENCY_CHECK для временных таблиц, убедитесь, что в вашем экземпляре установлено накопительное обновление 1 для SQL Server 2016.
Вот пример включения свойства DATA_CONSISTENCY_CHECK в существующих таблицах:
ALTER TABLE InMemoryExist SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.TableName_History, DATA_CONSISTENCY_CHECK = ON)) For a new table, DATA_CONSISTENCY_CHECK property enables after HISTORY_TABLE property separated by a comma. WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.DepartmentHistory, DATA_CONSISTENCY_CHECK = ON)); |
Заключение
Темпоральная таблица - это очень полезная функция SQL Server 2016, с помощью которой можно автоматизировать процессы с версионными строками. Это упрощает задачу архивирования данных и также может быть реальным решением для использования медленно меняющегося измерения для баз данных хранилища данных. Поскольку настраивать как новые, так и существующие таблицы так просто, функция Temporal Table является хорошим выбором для реализации с базами данных SQL Server.