History table implementation

As mentioned in the Creating temporal tables section, you can create the history table in advance, or let SQL Server create it for you. In the former case, the created table is a row stored table with a clustered index on period columns. If the current table does not contain data types that prevent the usage of data compression, the table is created with PAGE compression. This physical implementation is acceptable if you query the history table by using period columns as filter criteria. However, if your temporal queries usually look for historical records for individual rows, this is not a good implementation. To check potential performance issues of an automatic created history table, use the following code to create and populate a sample temporal table:

CREATE TABLE dbo.Mila
(
Id INT NOT NULL IDENTITY (1,1) PRIMARY KEY CLUSTERED,
C1 INT NOT NULL,
C2 NVARCHAR(4000) NULL
)
GO
INSERT INTO dbo.Mila(C1, C2) SELECT message_id, text FROM sys.messages WHERE language_id = 1033;
GO 50

ALTER TABLE dbo.Mila
ADD ValidFrom DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL CONSTRAINT DF_Mila_ValidFrom DEFAULT '20170101',
ValidTo DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL CONSTRAINT DF_Mila_ValidTo DEFAULT '99991231 23:59:59.9999999',
PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo);
GO
ALTER TABLE dbo.Mila SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.Mila_History));
GO

As you can see, the INSERT statement is repeated 50 times, so that the sample table has a lot of rows (661,150 rows). At this point, the history table is empty; all rows are in the current table only. To create a history row for each row in the current table, and one additional history row for the row with the ID of 1, use the following command:

UPDATE dbo.Mila SET C1 = C1 + 1;
UPDATE dbo.Mila SET C1 = 44 WHERE Id = 1;

Now, assume that you want to check the state of the row with the ID of 1 at some time in the past. Ensure also that query statistics parameters are set to on:

SET STATISTICS IO ON;
SET STATISTICS TIME ON;
SELECT * FROM dbo.Mila FOR SYSTEM_TIME AS OF '20170505 08:00:00' WHERE Id = 1;

This query returns one row:

Id  C1     C2                  ValidFrom           ValidTo
--- ----- ------------------- ---------------- -------------------
1 21 Warning:Fatal error 2017-01-01 00:00 2017-12-13 16:57
%d occurred

However, it is most interesting to check the execution parameters. The execution plan for the query is shown as follows:

Execution plan with a default index on the history table

You can see the Index Seek operator in the plan, but the plan is not efficient since it uses the ValidTo column as filter criteria. Observe the execution parameters:

SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 0 ms.
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 0 ms.

Table 'Mila_History'. Scan count 1, logical reads 10583, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Mila'. Scan count 0, logical reads 3, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:
CPU time = 188 ms, elapsed time = 193 ms.
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 0 ms.

SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 0 ms.

The query that returns a single row takes almost 200 milliseconds and SQL Server had to read more than 10,000 pages to generate this row! Since you have the Id column in the filter, it is clear that you need an index with this column as the leading column:

CREATE CLUSTERED INDEX ix_Mila_History ON dbo.Mila_History(Id, ValidTo, ValidFrom) WITH DROP_EXISTING;

When you execute the same query again, you can see the execution plan shown as follows:

Execution plan with a custom index on the history table

Actually, the plan looks the same, but the Id as the leading column in the clustered index on the history table allows SQL Server to search efficiently, as shown in the execution parameters' output:

SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 0 ms.
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 0 ms.

Table 'Mila_History'. Scan count 1, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Mila'. Scan count 0, logical reads 3, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 0 ms.
SQL Server parse and compile time:
CPU time = 0 ms, elapsed time = 0 ms.

SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 0 ms.

It is good when SQL Server can create and configure some objects for you, but you should not forget to check what it means for the performance of your queries.

Finally, if you plan to process a lot of data in temporal queries or to aggregate them, the best approach is to create your own history table with a clustered columnstore index and eventual, additional, non-clustered, normal B-tree indexes.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.128.226.121