Skip to content

Commit 1c46c46

Browse files
committed
Adding example for OPTIMIZE_FOR_SEQUENTIAL_KEY
1 parent f1cde85 commit 1c46c46

5 files changed

Lines changed: 187 additions & 0 deletions

File tree

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
![](./media/solutions-microsoft-logo-small.png)
2+
3+
# OPTIMIZE_FOR_SEQUENTIAL_KEY
4+
5+
In SQL Server 2019, a new index option was added called OPTIMIZE_FOR_SEQUENTIAL_KEY that is intended to address an issue known as [last page insert contention](https://support.microsoft.com/kb/4460004). Most of the solutions to this problem that have been suggested in the past involve making changes to either the application or the structure of the contentious index, which can be costly and sometimes involve performance trade-offs. Rather than making major structural changes, OPTIMIZE_FOR_SEQUENTIAL_KEY addresses some of the SQL Server scheduling issues that can lead to severely reduced throughput when last page insert contention occurs. Using the OPTMIZE_FOR_SEQUENTIAL_KEY index option can help maintain consistent throughput in high-concurrency environments when the following conditions are true:
6+
7+
- The index has a sequential key
8+
- The number of concurrent insert threads to the index far exceeds the number of schedulers (in other words logical cores)
9+
- The index has a high rate of new page allocations (page splits), which is most often due to a large row size
10+
11+
This sample illustrates how OPTIMIZE_FOR_SEQUENTIAL_KEY can be used to improve throughput on workloads that are suffering from severe last page insert contention bottlenecks.
12+
13+
### Contents
14+
15+
[About this sample](#about-this-sample)<br/>
16+
[Before you begin](#before-you-begin)<br/>
17+
[Run this sample](#run-this-sample)<br/>
18+
[Disclaimers](#disclaimers)<br/>
19+
[Related links](#related-links)<br/>
20+
21+
22+
<a name=about-this-sample></a>
23+
24+
## About this sample
25+
26+
- **Applies to:** SQL Server 2019 (or higher)
27+
- **Workload:** High-concurrency OLTP
28+
- **Programming Language:** T-SQL
29+
- **Authors:** Pam Lahoud
30+
- **Update history:** Created August 15, 2019
31+
32+
<a name=before-you-begin></a>
33+
34+
## Before you begin
35+
36+
To run this sample, you need the following prerequisites.
37+
38+
1. SQL Server 2019 (or higher)
39+
2. A server (physical or virtual) with multiple cores
40+
41+
[!NOTE]
42+
> This sample was designed for a server with 8 logical cores. If you run the sample on a server with more cores, you may need to increase the number of concurrent threads in order to observe the improvement.
43+
44+
45+
<a name=run-this-sample></a>
46+
47+
## Run this sample
48+
49+
1. Copy the files from the root folder to a folder on the SQL Server.
50+
51+
2. From SQL Server Management Studio or Azure Data Studio, run the Setup.sql script.
52+
53+
3. Modify the SequentialInserts_Optimized.bat and SequentialInserts_Unoptimized.bat files and change the -S parameter to point to the server where the setup script was run. For example, `-S.\SQL2019` points to an instance named SQL2019 on the local server.
54+
55+
4. Open the SQL2019_LatchWaits.htm file to open a Performance Monitor session in your default browser.
56+
57+
5. Right-click anywhere in the browser window to clear the existing data from the session.
58+
59+
6. Click the play button to start the Performance Monitor session.
60+
61+
7. From a Command Prompt, browse to the folder that contains the demo files and run SequentialInserts_Unoptimized.bat, then return to the Performance Monitor window. You should see a high number of Page Latch waits as well as high average wait times. Note the time it takes for the script to complete.
62+
63+
8. Run the SequentialInserts_Optimized.bat script from the same Command Prompt window and again return to the Performance Monitor window. This time you should see much lower number and duration of Page Latch waits, along with higher Batch requests/sec. Note the time it takes for the script to complete, it should be significantly faster than the Unoptimized script.
64+
65+
9. **OPTIONAL** - Modify the `-n256` parameter in the Optimized and Unoptimized scripts to see the effect on performance. Generally, the larger the number of concurrent sessions, the greater the improvement will be with OPTIMIZE_FOR_SEQUENTIAL_KEY.
66+
67+
68+
69+
<a name=disclaimers></a>
70+
71+
## Disclaimers
72+
The code included in this sample is not intended to be a set of best practices on how to build scalable enterprise grade applications. This is beyond the scope of this quick start sample.
73+
74+
<a name=related-links></a>
75+
76+
## Related Links
77+
78+
For more information, see these articles:
79+
80+
[CREATE INDEX - Sequential Keys](https://docs.microsoft.com/sql/t-sql/statements/create-index-transact-sql#sequential-keys)
Binary file not shown.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
ECHO OFF
2+
rd /s /q %temp%\output
3+
"ostress.exe" -E -S.\SQL2019 -dSequentialKeySampleDB -Q"EXEC usp_InsertLogRecord @Optimized = 1" -mstress -quiet -n1 -r1 | FINDSTR "Cantfindthisstring"
4+
rd /s /q %temp%\output
5+
"ostress.exe" -E -S.\SQL2019 -dSequentialKeySampleDB -Q"EXEC usp_InsertLogRecord @Optimized = 1" -mstress -quiet -n256 -r250 | FINDSTR "QEXEC Starting Creating elapsed"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
ECHO OFF
2+
rd /s /q %temp%\output
3+
"ostress.exe" -E -S.\SQL2019 -dSequentialKeySampleDB -Q"EXEC usp_InsertLogRecord" -mstress -quiet -n1 -r1 | FINDSTR "Cantfindthisstring"
4+
rd /s /q %temp%\output
5+
"ostress.exe" -E -S.\SQL2019 -dSequentialKeySampleDB -Q"EXEC usp_InsertLogRecord" -mstress -quiet -n256 -r250 | FINDSTR "QEXEC Starting Creating elapsed"
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
USE master;
2+
GO
3+
4+
CREATE DATABASE SequentialKeySampleDB;
5+
GO
6+
7+
USE SequentialKeySampleDB;
8+
GO
9+
10+
-- Create regular table
11+
12+
DROP TABLE IF EXISTS [dbo].[TestSequentialKey];
13+
GO
14+
15+
CREATE TABLE [dbo].[TestSequentialKey](
16+
[DatabaseLogID] [bigint] IDENTITY(1,1) NOT NULL,
17+
[PostTime] [datetime2] NOT NULL,
18+
[DatabaseUser] [sysname] NOT NULL,
19+
[Event] [sysname] NOT NULL,
20+
[Schema] [sysname] NULL,
21+
[Object] [sysname] NULL,
22+
[TSQL] [nvarchar](max) NOT NULL
23+
CONSTRAINT [PK_TestSequentialKey_DatabaseLogID] PRIMARY KEY NONCLUSTERED
24+
(
25+
[DatabaseLogID] ASC
26+
));
27+
28+
CREATE CLUSTERED INDEX CIX_TestSequentialKey_PostTime ON TestSequentialKey (PostTime);
29+
GO
30+
31+
-- Create optimized table
32+
33+
DROP TABLE IF EXISTS [dbo].[TestSequentialKey_Optimized];
34+
GO
35+
36+
CREATE TABLE [dbo].[TestSequentialKey_Optimized](
37+
[DatabaseLogID] [bigint] IDENTITY(1,1) NOT NULL,
38+
[PostTime] [datetime2] NOT NULL,
39+
[DatabaseUser] [sysname] NOT NULL,
40+
[Event] [sysname] NOT NULL,
41+
[Schema] [sysname] NULL,
42+
[Object] [sysname] NULL,
43+
[TSQL] [nvarchar](max) NOT NULL
44+
CONSTRAINT [PK_TestSequentialKey_Optimized_DatabaseLogID] PRIMARY KEY NONCLUSTERED
45+
(
46+
[DatabaseLogID] ASC
47+
)
48+
WITH (OPTIMIZE_FOR_SEQUENTIAL_KEY=ON));
49+
50+
CREATE CLUSTERED INDEX CIX_TestSequentialKey_Optimized_PostTime ON TestSequentialKey_Optimized (PostTime) WITH (OPTIMIZE_FOR_SEQUENTIAL_KEY=ON);
51+
GO
52+
53+
-- Create INSERT stored procedure
54+
55+
CREATE OR ALTER PROCEDURE usp_InsertLogRecord @Optimized bit = 0 AS
56+
57+
DECLARE @PostTime datetime2 = SYSDATETIME(), @User sysname, @Event sysname, @Schema sysname, @Object sysname, @TSQL nvarchar(max)
58+
59+
SELECT @User = name
60+
FROM sys.sysusers
61+
WHERE issqlrole = 0 and hasdbaccess = 1 and status = 0
62+
ORDER BY NEWID();
63+
64+
SELECT @Object = t.name, @Schema = s.name
65+
FROM sys.tables t
66+
INNER JOIN sys.schemas s ON s.schema_id = t.schema_id
67+
ORDER BY NEWID();
68+
69+
IF DATEPART(ms, @PostTime) % 4 = 0
70+
BEGIN
71+
SET @Event = N'SELECT';
72+
SET @TSQL = N'SELECT * FROM ' + @Schema + '.' + @Object
73+
END
74+
ELSE IF DATEPART(ms, @PostTime) % 4 = 1
75+
BEGIN
76+
SET @Event = N'INSERT';
77+
SET @TSQL = N'INSERT ' + @Schema + '.' + @Object + ' SELECT * FROM ' + @Schema + '.' + @Object
78+
END
79+
ELSE IF DATEPART(ms, @PostTime) % 4 = 2
80+
BEGIN
81+
SET @Event = N'UPDATE';
82+
SET @TSQL = N'UPDATE ' + @Schema + '.' + @Object + ' SET 1=1';
83+
END
84+
ELSE IF DATEPART(ms, @PostTime) % 4 = 3
85+
BEGIN
86+
SET @Event = N'DELETE';
87+
SET @TSQL = N'DELETE FROM ' + @Schema + '.' + @Object + ' WHERE 1=1';
88+
END
89+
90+
IF @Optimized = 1
91+
INSERT TestSequentialKey_Optimized (PostTime, DatabaseUser, [Event], [Schema], [Object], [TSQL])
92+
VALUES (@PostTime, @User, @Event, @Schema, @Object, @TSQL);
93+
ELSE
94+
INSERT TestSequentialKey (PostTime, DatabaseUser, [Event], [Schema], [Object], [TSQL])
95+
VALUES (@PostTime, @User, @Event, @Schema, @Object, @TSQL);
96+
97+
GO

0 commit comments

Comments
 (0)