Files
sql-server-samples/samples/features/intelligent-query-processing/notebooks/Batch_Mode_on_Rowstore.ipynb
T

116 KiB

Batch Mode on rowstore

Batch mode on rowstore enables batch mode execution for analytic workloads without requiring columnstore indexes. This feature supports batch mode execution and bitmap filters for on-disk heaps and B-tree indexes. Batch mode on rowstore enables support for all existing batch mode-enabled operators. Batch mode on Rowstore is a feature under the Intelligent Query Processing suite of features.

This example will show you how upgrading to Database Compatibility Level 150 could improve performance due to batch mode on rowstore, when your workload has the following characteristics:

  • A significant part of the workload consists of analytical queries. Usually, these queries have operators like joins or aggregates that process hundreds of thousands of rows or more.
  • The workload is CPU bound (If the bottleneck is I/O, we still recommend that you consider a columnstore index, if possible).
  • If creating a columnstore index would add too much overhead to the transactional part of your workload; or if creating a columnstore index isn't feasible because your application depends on a feature that's not yet supported with columnstore indexes.

More information about this feature is available here.

Step 1: Setup WideWorldImportersDW database

You could choose to use a container to evaluate this feature. Create an instance of SQL Server 2019 using a Docker image and restore the WideWorldImportersDW database backup

You will need the WideWorldImportersDW database for this exercise. If you don't have this sample database, then you download the sample database here.

Restore the copied WideWorldImportersDW database backup into the container and restore the backup.

Docker Commands
docker pull mcr.microsoft.com/mssql/server:2019-latest

docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=`<A Strong Password`>" -p 1445:1433 --name sql2019demo -d mcr.microsoft.com/mssql/server:2019-latest

docker cp ".\Downloads\WideWorldImportersDW-Full.bak" sql2019demo:/var/opt/mssql/data

Note: For Linux installations the default path to use is /var/opt/mssql

In [7]:
USE [master]
GO
IF EXISTS (SELECT [database_id] FROM sys.databases WHERE [name] = 'WideWorldImportersDW')
ALTER DATABASE [WideWorldImportersDW] SET SINGLE_USER WITH ROLLBACK IMMEDIATE
GO

DECLARE @datafilepath VARCHAR(8000) = CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS VARCHAR(4000)) + 'WideWorldImportersDW.mdf'
DECLARE @logfilepath VARCHAR(8000) = CAST(SERVERPROPERTY('InstanceDefaultLogPath') AS VARCHAR(4000)) + 'WideWorldImportersDW.ldf'
DECLARE @inmemfilepath VARCHAR(8000) = CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS VARCHAR(4000)) + 'WideWorldImportersDW_InMemory_Data_1'
DECLARE @secondaryfilepath VARCHAR(8000) = CAST(SERVERPROPERTY('InstanceDefaultDataPath') AS VARCHAR(4000))+ 'WideWorldImportersDW_2.ndf'

-- Change @backupfile file path as needed
DECLARE @backupfile VARCHAR(8000) = 'E:\SampleDBs\WideWorldImportersDW-Full.bak'
RESTORE DATABASE WideWorldImportersDW
FROM DISK = @backupfile 
WITH MOVE 'WWI_Primary' TO @datafilepath,
    MOVE 'WWI_UserData' TO @secondaryfilepath,
    MOVE 'WWIDW_InMemory_Data_1' TO @inmemfilepath,
    MOVE 'WWI_Log' TO @logfilepath, NOUNLOAD, REPLACE, STATS = 10
GO

USE [master]
GO
ALTER DATABASE [WideWorldImportersDW] MODIFY FILE ( NAME = N'WWI_Log', SIZE = 4GB )
GO

Step 2: Enlarge the WideWorldImportersDW database

In [3]:
USE WideWorldImportersDW;
GO

/*
Assumes a fresh restore of WideWorldImportersDW
*/

IF OBJECT_ID('Fact.OrderHistory') IS NULL 
BEGIN
    SELECT [Order Key], [City Key], [Customer Key], [Stock Item Key], [Order Date Key], [Picked Date Key], [Salesperson Key], [Picker Key], [WWI Order ID], [WWI Backorder ID], Description, Package, Quantity, [Unit Price], [Tax Rate], [Total Excluding Tax], [Tax Amount], [Total Including Tax], [Lineage Key]
    INTO Fact.OrderHistory
    FROM Fact.[Order];
END;

ALTER TABLE Fact.OrderHistory
ADD CONSTRAINT PK_Fact_OrderHistory PRIMARY KEY NONCLUSTERED ([Order Key] ASC, [Order Date Key] ASC) WITH (DATA_COMPRESSION = PAGE);
GO

CREATE INDEX IX_Stock_Item_Key
ON Fact.OrderHistory ([Stock Item Key])
INCLUDE(Quantity)
WITH (DATA_COMPRESSION = PAGE);
GO

CREATE INDEX IX_OrderHistory_Quantity
ON Fact.OrderHistory ([Quantity])
INCLUDE([Order Key])
WITH (DATA_COMPRESSION = PAGE);
GO

CREATE INDEX IX_OrderHistory_CustomerKey
ON Fact.OrderHistory([Customer Key])
INCLUDE ([Total Including Tax])
WITH (DATA_COMPRESSION = PAGE);
GO

IF (SELECT COUNT(*) FROM [Fact].[OrderHistory]) < 3702592
BEGIN
	DECLARE @i smallint
	SET @i = 0
	WHILE @i < 4
	BEGIN
		INSERT INTO [Fact].[OrderHistory] ([City Key], [Customer Key], [Stock Item Key], [Order Date Key], [Picked Date Key], [Salesperson Key], [Picker Key], [WWI Order ID], [WWI Backorder ID], Description, Package, Quantity, [Unit Price], [Tax Rate], [Total Excluding Tax], [Tax Amount], [Total Including Tax], [Lineage Key])
		SELECT [City Key], [Customer Key], [Stock Item Key], [Order Date Key], [Picked Date Key], [Salesperson Key], [Picker Key], [WWI Order ID], [WWI Backorder ID], Description, Package, Quantity, [Unit Price], [Tax Rate], [Total Excluding Tax], [Tax Amount], [Total Including Tax], [Lineage Key]
		FROM [Fact].[OrderHistory];

		SET @i = @i +1
	END;
END
GO

IF OBJECT_ID('Fact.OrderHistoryExtended') IS NULL 
BEGIN
    SELECT [Order Key], [City Key], [Customer Key], [Stock Item Key], [Order Date Key], [Picked Date Key], [Salesperson Key], [Picker Key], [WWI Order ID], [WWI Backorder ID], Description, Package, Quantity, [Unit Price], [Tax Rate], [Total Excluding Tax], [Tax Amount], [Total Including Tax], [Lineage Key]
    INTO Fact.OrderHistoryExtended
    FROM Fact.[OrderHistory];
END;

ALTER TABLE Fact.OrderHistoryExtended
ADD CONSTRAINT PK_Fact_OrderHistoryExtended PRIMARY KEY NONCLUSTERED ([Order Key] ASC, [Order Date Key] ASC)
WITH (DATA_COMPRESSION = PAGE);
GO

CREATE INDEX IX_Stock_Item_Key
ON Fact.OrderHistoryExtended ([Stock Item Key])
INCLUDE (Quantity);
GO

IF (SELECT COUNT(*) FROM [Fact].[OrderHistory]) < 29620736
BEGIN
	DECLARE @i smallint
	SET @i = 0
	WHILE @i < 3
	BEGIN
		INSERT Fact.OrderHistoryExtended([City Key], [Customer Key], [Stock Item Key], [Order Date Key], [Picked Date Key], [Salesperson Key], [Picker Key], [WWI Order ID], [WWI Backorder ID], Description, Package, Quantity, [Unit Price], [Tax Rate], [Total Excluding Tax], [Tax Amount], [Total Including Tax], [Lineage Key])
		SELECT [City Key], [Customer Key], [Stock Item Key], [Order Date Key], [Picked Date Key], [Salesperson Key], [Picker Key], [WWI Order ID], [WWI Backorder ID], Description, Package, Quantity, [Unit Price], [Tax Rate], [Total Excluding Tax], [Tax Amount], [Total Including Tax], [Lineage Key]
		FROM Fact.OrderHistoryExtended;

		SET @i = @i +1
	END;
END
GO

UPDATE Fact.OrderHistoryExtended
SET [WWI Order ID] = [Order Key];
GO

-- Repeat the following until log shrinks. These demos don't require much log space.
CHECKPOINT
GO
DBCC SHRINKFILE (N'WWI_Log' , 0, TRUNCATEONLY)
GO
SELECT * FROM sys.dm_db_log_space_usage
GO

Step 3: Execute the query without Batch Mode on Rowstore

Even when the database compatibility level is set to the defauult (150), this can be done by using the USE HINT DISALLOW_BATCH_MODE query hint to disable the feature.

In [2]:
USE [WideWorldImportersDW];
GO
ALTER DATABASE [WideWorldImportersDW] SET COMPATIBILITY_LEVEL = 150;
GO
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
GO

-- Row mode due to hint
SELECT [Tax Rate],
		[Lineage Key],
		[Salesperson Key],
	SUM([Quantity]) AS SUM_QTY,
	SUM([Unit Price]) AS SUM_BASE_PRICE,
	COUNT(*) AS COUNT_ORDER
FROM [Fact].[OrderHistoryExtended]
WHERE [Order Date Key]	<= dateadd(dd, -73, '2015-11-13')
GROUP BY [Tax Rate],
		[Lineage Key],
		[Salesperson Key]
ORDER BY [Tax Rate],
		[Lineage Key],
		[Salesperson Key]
OPTION (RECOMPILE, USE HINT('DISALLOW_BATCH_MODE'));
GO
Out [2]:
Commands completed successfully.
Commands completed successfully.
Commands completed successfully.
(100 rows affected)
Total execution time: 00:00:04.930
Tax RateLineage KeySalesperson KeySUM_QTYSUM_BASE_PRICECOUNT_ORDER
15.00094433280243367.689216
15.00096836480543988.4818432
15.00097724480846118.4024576
15.000988620801028780.8022144
15.000991101056924917.7628928
15.00091122792963886913.2862848
15.00091232625923852878.0885888
15.00091554848006343098.88144256
15.00091991831040107590420.482310528
15.00092112409602060285.4437632
15.00092310035201541052.1629696
15.00092529866242914885.1273344
15.000926497536725172.4812416
15.000927598016798522.8814592
15.00092812331521474490.8831488
15.000929387968304568.3210880
15.00093015493122156144.6447232
15.00093122152962227068.1650560
15.00093527247363383616.0072960
15.0009361101632012551537.92276992
15.00093815197441955315.2039552
15.0009393188838434617245.44785664
15.00094068074247944593.92176896
15.00094122191363174849.2863232
15.000945338176382839.047808
15.000946112128111915.522176
15.00094732556802629954.5675776
15.00094825959682310150.4060928
15.0009491475852817928881.92368768
15.000950804352926225.9219968
15.00095140078084360591.3697408
15.00095258634247464648.96156160
15.00095427367683351491.8472320
15.000956285824253144.327552
15.00095722982402447610.8858240
15.00095821447681723477.7650048
15.00096137322244086886.4086016
15.0009621267212813904339.20304000
15.0009631012620810138237.44243200
15.00096567874567473853.44171136
15.00096846387204848647.68107008
15.0009701488486416764665.60379776
15.00097225658882336160.0055936
15.0009741531788819187417.60384768
15.00097664812808637446.40169088
15.00097771283207693249.28169984
15.00097850613765629213.44121856
15.00098077431048722903.04203264
15.0009812880806432882525.44706048
15.0009831266227214586407.68330880
15.00098465091848482062.08169856
15.00098547825924991431.68116864
15.0009862702169630225996.80682624
15.00098915787522027873.2836096
15.0009903380646439663014.40856704
15.00099144427525842634.24112768
15.0009932820019233006179.84713984
15.0009941268160013601986.56303232
15.000996666624473141.7614464
15.0009971199308813300569.60303104
15.000910175751687600646.40196992
15.00091022744192033995997.44703872
15.000910370572808503637.76185088
15.00091052971084834073157.12735872
15.000910681047049812775.68220416
15.000910725904643028149.7661952
15.00091084015616045593496.321001600
15.000911524520962226137.6060800
15.00091162324339224868823.04564736
15.00091181424012815111184.64353408
15.00091194084672045264224.001027456
15.000912267325447716145.92164992
15.000912670828808110862.08171008
15.00091272421171228525308.16616576
15.00091293143731234298964.48767488
15.0009130390272315754.2410880
15.00091341959820822598796.80491520
15.0009136357248296313.609856
15.000913846970885400261.12118144
15.00091401732198419747557.12433280
15.00091432200729626812788.48567040
15.000914477776649402172.16193152
15.00091451539968017946855.68388096
15.000914922568962663919.3660416
15.00091501086963212971114.24281344
15.00091511113792011154696.96269568
15.00091521170048013719448.32285056
15.000915482813448556992.00212864
15.000915589514249563397.12219264
15.000915660046085900654.08152192
15.000915850048065113.604352
15.000916125395256025.602816
15.000916324832048179.202688
15.000916510201612416.00896
15.0009167168964608.00256
15.000916814118421619.201408
15.00091707577613760.00768
15.000917111251214681.601024
15.00091727782410137.60768
15.000917476802304.00128

Observe the query execution plan (or actual plan).

BMOR_Disabled_Plan

Notice the time spent in each operator (cumulative up the tree for a query in row mode). Also note the two Aggregate operators. The Hash Match only does a partial aggregate, so later in the execxution a Stream Aggregate is needed to fully aggregate the result set as intended. Stream Aggregates are only possible in Row Mode.

Confirm the Actual Execution Mode was indeed "Row", for example in the properties of the Table Scan operator.

BMOR_TableScan_properties

Step 4: Execute the query by removing any hint restriction

Run the same query from Step 3, but now without any hint, allowing SQL Server to operate as default. If a query is eligible for Batch Mode on Rowstore, it will be used instead of Row Mode.

In [3]:
ALTER DATABASE [WideWorldImportersDW] SET COMPATIBILITY_LEVEL = 150;
GO

ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
GO

USE [WideWorldImportersDW]
GO
-- Batch mode on rowstore eligible
SELECT [Tax Rate],
		[Lineage Key],
		[Salesperson Key],
	SUM([Quantity]) AS SUM_QTY,
	SUM([Unit Price]) AS SUM_BASE_PRICE,
	COUNT(*) AS COUNT_ORDER
FROM [Fact].[OrderHistoryExtended]
WHERE [Order Date Key]	<= dateadd(dd, -73, '2015-11-13')
GROUP BY [Tax Rate],
		[Lineage Key],
		[Salesperson Key]
ORDER BY [Tax Rate],
		[Lineage Key],
		[Salesperson Key]
OPTION (RECOMPILE);
Out [3]:
Commands completed successfully.
Commands completed successfully.
Commands completed successfully.
(100 rows affected)
Total execution time: 00:00:01.818
Tax RateLineage KeySalesperson KeySUM_QTYSUM_BASE_PRICECOUNT_ORDER
15.00094433280243367.689216
15.00096836480543988.4818432
15.00097724480846118.4024576
15.000988620801028780.8022144
15.000991101056924917.7628928
15.00091122792963886913.2862848
15.00091232625923852878.0885888
15.00091554848006343098.88144256
15.00091991831040107590420.482310528
15.00092112409602060285.4437632
15.00092310035201541052.1629696
15.00092529866242914885.1273344
15.000926497536725172.4812416
15.000927598016798522.8814592
15.00092812331521474490.8831488
15.000929387968304568.3210880
15.00093015493122156144.6447232
15.00093122152962227068.1650560
15.00093527247363383616.0072960
15.0009361101632012551537.92276992
15.00093815197441955315.2039552
15.0009393188838434617245.44785664
15.00094068074247944593.92176896
15.00094122191363174849.2863232
15.000945338176382839.047808
15.000946112128111915.522176
15.00094732556802629954.5675776
15.00094825959682310150.4060928
15.0009491475852817928881.92368768
15.000950804352926225.9219968
15.00095140078084360591.3697408
15.00095258634247464648.96156160
15.00095427367683351491.8472320
15.000956285824253144.327552
15.00095722982402447610.8858240
15.00095821447681723477.7650048
15.00096137322244086886.4086016
15.0009621267212813904339.20304000
15.0009631012620810138237.44243200
15.00096567874567473853.44171136
15.00096846387204848647.68107008
15.0009701488486416764665.60379776
15.00097225658882336160.0055936
15.0009741531788819187417.60384768
15.00097664812808637446.40169088
15.00097771283207693249.28169984
15.00097850613765629213.44121856
15.00098077431048722903.04203264
15.0009812880806432882525.44706048
15.0009831266227214586407.68330880
15.00098465091848482062.08169856
15.00098547825924991431.68116864
15.0009862702169630225996.80682624
15.00098915787522027873.2836096
15.0009903380646439663014.40856704
15.00099144427525842634.24112768
15.0009932820019233006179.84713984
15.0009941268160013601986.56303232
15.000996666624473141.7614464
15.0009971199308813300569.60303104
15.000910175751687600646.40196992
15.00091022744192033995997.44703872
15.000910370572808503637.76185088
15.00091052971084834073157.12735872
15.000910681047049812775.68220416
15.000910725904643028149.7661952
15.00091084015616045593496.321001600
15.000911524520962226137.6060800
15.00091162324339224868823.04564736
15.00091181424012815111184.64353408
15.00091194084672045264224.001027456
15.000912267325447716145.92164992
15.000912670828808110862.08171008
15.00091272421171228525308.16616576
15.00091293143731234298964.48767488
15.0009130390272315754.2410880
15.00091341959820822598796.80491520
15.0009136357248296313.609856
15.000913846970885400261.12118144
15.00091401732198419747557.12433280
15.00091432200729626812788.48567040
15.000914477776649402172.16193152
15.00091451539968017946855.68388096
15.000914922568962663919.3660416
15.00091501086963212971114.24281344
15.00091511113792011154696.96269568
15.00091521170048013719448.32285056
15.000915482813448556992.00212864
15.000915589514249563397.12219264
15.000915660046085900654.08152192
15.000915850048065113.604352
15.000916125395256025.602816
15.000916324832048179.202688
15.000916510201612416.00896
15.0009167168964608.00256
15.000916814118421619.201408
15.00091707577613760.00768
15.000917111251214681.601024
15.00091727782410137.60768
15.000917476802304.00128

Observe the query execution plan (or actual plan).

BMOR_Enabled_Plan

Notice the time spent in each operator (not cumulative up the tree for a query in batch mode). Note the Stream Aggregate operator is not present. Also note that only one aggregate was required for the Batch Mode plan, which also improves the query execution.

Confirm the Actual Execution Mode was indeed "Batch", for example in the properties of the Table Scan operator.

BMOR_enabled_TableScan_properties

As you can see from the execution times, the query with Batch Mode for Rowstore finished much faster! From ~5s to ~1.8s.