diff --git a/samples/databases/README.md b/samples/databases/README.md index 0f959213..016a175f 100644 --- a/samples/databases/README.md +++ b/samples/databases/README.md @@ -9,3 +9,7 @@ The new sample database for SQL Server 2016 and Azure SQL Database. It illustrat __[contoso-data-warehouse](contoso-data-warehouse/)__ Sample data warehouse that illustrates loading data into Azure SQL Data Warehouse. + +__[AdventureWorks2014](https://github.com/Microsoft/sql-server-samples/releases/tag/adventureworks2014)__ + +Sample databases and Analysis Services models for use with SQL Server 2014 and later. \ No newline at end of file diff --git a/samples/features/automatic-tuning/force-last-good-plan/force-last-good-plan.csproj b/samples/features/automatic-tuning/force-last-good-plan/force-last-good-plan.csproj new file mode 100644 index 00000000..809a941a --- /dev/null +++ b/samples/features/automatic-tuning/force-last-good-plan/force-last-good-plan.csproj @@ -0,0 +1,36 @@ + + + + netcoreapp1.0 + true + force-last-good-plan + Exe + force-last-good-plan + 1.0.4 + $(PackageTargetFallback);dotnet5.6;portable-net45+win8 + + + + + PreserveNewest + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/demo-full.sql b/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/demo-full.sql index 0fed3f0b..1dd38d35 100644 --- a/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/demo-full.sql +++ b/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/demo-full.sql @@ -1,60 +1,74 @@ /******************************************************** * SETUP - clear everything ********************************************************/ -EXEC [dbo].[initialize] - +ALTER DATABASE current SET AUTOMATIC_TUNING (FORCE_LAST_GOOD_PLAN = OFF); +EXEC dbo.initialize; /******************************************************** * PART I -* Plan regression identification. +* Plan regression identification & manual tuning ********************************************************/ --- 1. Start workload - execute procedure 30-300 times: -begin -declare @packagetypeid int = 7; -exec dbo.report @packagetypeid -end -go 300 --- Queries should be fast --- Optionally, include "Actual execution plan" in SSMS and show the plan (it should have Hash Aggregate) +-- Execute the query and include "Actual execution plan" in SSMS and show the plan - it should have Hash Match (Aggregate) operator with Columnstore Index Scan +EXEC sp_executesql N'select avg([UnitPrice]*[Quantity]) + from Sales.OrderLines + where PackageTypeID = @packagetypeid', N'@packagetypeid int', @packagetypeid = 7; +GO 60 +-- 1. Execute this query 45-300 times to setup the baseline. +-- If you have QUERY_STORE CAPTURE_POLICY=AUTO increase number in GO to at least 60 --- 2. Execute procedure that causes plan regression --- Optionally, include "Actual execution plan" in SSMS and show the plan (it should have Stream Aggregate) -exec dbo.regression +-- 2. Execute the procedure that causes plan regression +-- Optionally, include "Actual execution plan" in SSMS and show the plan - it should have Stream Aggregate, Index Seek & Nested Loops +EXEC dbo.regression; --- 3. Start workload again - verify that is slower. -begin -declare @packagetypeid int = 7; -exec dbo.report @packagetypeid -end +-- 3. Start the workload again - verify that is slower. +EXEC sp_executesql N'select avg([UnitPrice]*[Quantity]) + from Sales.OrderLines + where PackageTypeID = @packagetypeid', N'@packagetypeid int', @packagetypeid = 7; go 20 --- Optionally, include "Actual execution plan" in SSMS and show the plan (it should have Stream Aggregate) +-- Optionally, include "Actual execution plan" in SSMS and show the plan - it should have Stream Aggregate with Non-clustered index seek. --- 4. Find recommendation recommended by database: -SELECT planForceDetails.query_id, reason, score, - JSON_VALUE(details, '$.implementationDetails.script') [correction script], - planForceDetails.[new plan_id], planForceDetails.[recommended plan_id] -FROM sys.dm_db_tuning_recommendations - CROSS APPLY OPENJSON (Details, '$.planForceDetails') - WITH ( [query_id] int '$.queryId', - [new plan_id] int '$.regressedPlanId', - [recommended plan_id] int '$.recommendedPlanId' - ) as planForceDetails; +-- 4. Find a recommendation that can fix this issue: +SELECT reason, score, + script = JSON_VALUE(details, '$.implementationDetails.script') + FROM sys.dm_db_tuning_recommendations; +-- 4.1. Optionally get more detailed information about the regression and recommendation. +SELECT reason, score, + script = JSON_VALUE(details, '$.implementationDetails.script'), + planForceDetails.[query_id], + planForceDetails.[new plan_id], + planForceDetails.[recommended plan_id], + estimated_gain = (regressedPlanExecutionCount+recommendedPlanExecutionCount)*(regressedPlanCpuTimeAverage-recommendedPlanCpuTimeAverage)/1000000, + error_prone = IIF(regressedPlanErrorCount>recommendedPlanErrorCount, 'YES','NO') + FROM sys.dm_db_tuning_recommendations + CROSS APPLY OPENJSON (Details, '$.planForceDetails') + WITH ( [query_id] int '$.queryId', + [new plan_id] int '$.regressedPlanId', + [recommended plan_id] int '$.recommendedPlanId', + regressedPlanErrorCount int, + recommendedPlanErrorCount int, + regressedPlanExecutionCount int, + regressedPlanCpuTimeAverage float, + recommendedPlanExecutionCount int, + recommendedPlanCpuTimeAverage float ) as planForceDetails; +-- IMPORTANT NOTE: check is estimated_gain > 10. +-- If estimated_gain < 10 THEN FLGP=ON will not automatically force the plan!!! +-- In that case increase the number of executions in initial workload. +-- Make sure that SQL Engine uses columnstore in original plan and nonclustered index in regressed plan. -- Note: User can apply script and force the recommended plan to correct the error. <> -- e.g.: exec sp_query_store_force_plan @query_id = 3, @plan_id = 1 --- 5. Start workload again - verify that is faster. -begin -declare @packagetypeid int = 7; -exec dbo.report @packagetypeid -end -go 20 --- Optionally, include "Actual execution plan" in SSMS and show the plan (it should have Hash Aggregate again) +-- 5. Execute the query again - verify that it is faster. +EXEC sp_executesql N'select avg([UnitPrice]*[Quantity]) + from Sales.OrderLines + where PackageTypeID = @packagetypeid', N'@packagetypeid int', @packagetypeid = 7; +GO 20 +-- Optionally, include "Actual execution plan" in SSMS and show the plan - it should have Hash Aggregate & Columnstore again -- In part II will be shown better approach - automatic tuning. @@ -74,29 +88,26 @@ ALTER DATABASE current SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = ON); -- Verify that actual state on FLGP is ON: -SELECT name, desired_state_desc, actual_state_desc, reason_desc -FROM sys.database_automatic_tuning_options; - +SELECT name, actual_state_desc, status = IIF(desired_state_desc <> actual_state_desc, reason_desc, 'Status:OK') +FROM sys.database_automatic_tuning_options +WHERE name = 'FORCE_LAST_GOOD_PLAN'; -- 1. Start workload - execute procedure 30-300 times like in the phase I -begin -declare @packagetypeid int = 7; -exec dbo.report @packagetypeid -end -go 300 +EXEC sp_executesql N'select avg([UnitPrice]*[Quantity]) + from Sales.OrderLines + where PackageTypeID = @packagetypeid', N'@packagetypeid int', @packagetypeid = 7; +GO 60 --- 2. Execute the procedure that causes plan regression +-- 2. Execute the procedure that causes the plan regression exec dbo.regression; --- 3. Start workload again - verify that it is slower. -begin -declare @packagetypeid int = 7; -exec dbo.report @packagetypeid; -end -go 20 +-- 3. Start the workload again - verify that it is slower. +EXEC sp_executesql N'select avg([UnitPrice]*[Quantity]) + from Sales.OrderLines + where PackageTypeID = @packagetypeid', N'@packagetypeid int', @packagetypeid = 7; +go 30 --- 4. Find recommendation that returns query perf regression --- and check is it in Verifying state: +-- 4. Find a recommendation and check is it in "Verifying" or "Success" state: SELECT reason, score, JSON_VALUE(state, '$.currentValue') state, JSON_VALUE(state, '$.reason') state_transition_reason, @@ -110,11 +121,11 @@ FROM sys.dm_db_tuning_recommendations ) as planForceDetails; --- 5. Wait until recommendation is applied and start workload again - verify that it is faster. -begin -declare @packagetypeid int = 7; -exec dbo.report @packagetypeid -end -go 30 +-- 5. Recommendation is in "Verifying" state, but the last good plan is forced, so the query will be faster: +EXEC sp_executesql N'select avg([UnitPrice]*[Quantity]) + from Sales.OrderLines + where PackageTypeID = @packagetypeid', N'@packagetypeid int', @packagetypeid = 7; + + +-- Open Query Store/"Top Resource Consuming Queries" dialog in SSMS and show that the better plan is forced. --- Open Query Store/"Top Resource Consuming Queries" dialog in SSMS and show that better plan is forced. \ No newline at end of file diff --git a/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/init-blank-db.sql b/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/init-blank-db.sql new file mode 100644 index 00000000..95063b8d --- /dev/null +++ b/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/init-blank-db.sql @@ -0,0 +1,64 @@ +/*************************************************************************** +* Run this script on a empty database if you don't have WWI database and +* you want to use new database instead of full WWI +* If you are using SSMS, use Ctrl+Shift+M to populate parameters. +***************************************************************************/ +ALTER DATABASE MODIFY (EDITION = 'Premium', SERVICE_OBJECTIVE = ''); +SELECT DATABASEPROPERTYEX('', 'ServiceObjective'); +-- Create minimal WWI schema required to run the sample: +DROP TABLE IF EXISTS [Sales].[OrderLines]; +GO +DROP SEQUENCE IF EXISTS [Sequences].[OrderLineID]; +GO +DROP SCHEMA IF EXISTS [Sequences]; +GO +DROP SCHEMA IF EXISTS [Sequences]; +GO + +CREATE SCHEMA [Sequences]; +GO +CREATE SEQUENCE [Sequences].[OrderLineID] + AS [int] + START WITH 231413 + INCREMENT BY 1 + MINVALUE -2147483648 + MAXVALUE 2147483647 + CACHE +GO + +CREATE TABLE [Sales].[OrderLines]( + [OrderLineID] [int] PRIMARY KEY, + [OrderID] [int] NOT NULL, + [StockItemID] [int] NOT NULL, + [Description] [nvarchar](100) NOT NULL, + [PackageTypeID] [int] NOT NULL, + [Quantity] [int] NOT NULL, + [UnitPrice] [decimal](18, 2) NULL, + [TaxRate] [decimal](18, 3) NOT NULL, + [PickedQuantity] [int] NOT NULL, + [PickingCompletedWhen] [datetime2](7) NULL, + [LastEditedBy] [int] NOT NULL, + [LastEditedWhen] [datetime2](7) NOT NULL +) +GO +ALTER TABLE [Sales].[OrderLines] + ADD CONSTRAINT [DF_Sales_OrderLines_OrderLineID] + DEFAULT (NEXT VALUE FOR [Sequences].[OrderLineID]) FOR [OrderLineID] +GO +ALTER TABLE [Sales].[OrderLines] + ADD CONSTRAINT [DF_Sales_OrderLines_LastEditedWhen] + DEFAULT (sysdatetime()) FOR [LastEditedWhen] +GO +DROP INDEX IF EXISTS [FK_Sales_OrderLines_PackageTypeID] + ON [Sales].[OrderLines] + +CREATE NONCLUSTERED INDEX [FK_Sales_OrderLines_PackageTypeID] + ON [Sales].[OrderLines]([PackageTypeID] ASC) +GO + + +-- Export Sales.OrderLines from WWI database using bcp: +-- bcp WideWorldImporters.Sales.OrderLines out OrderLines.dat -T -c -U -P -S + +-- Import data in new database using bcp: +-- bcp .Sales.OrderLines in OrderLines.dat -c -U -P -S diff --git a/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/setup.sql b/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/setup.sql index b1dd01a1..382ef397 100644 --- a/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/setup.sql +++ b/samples/features/automatic-tuning/force-last-good-plan/sql-scripts/setup.sql @@ -1,6 +1,10 @@ -DROP INDEX IF EXISTS [NCCX_Sales_OrderLines] ON [Sales].[OrderLines] +-- Insert one OrderLine that with PackageTypeID=(0) will cause regression +INSERT INTO Sales.OrderLines(OrderId, StockItemID, Description, PAckageTypeID, quantity, unitprice, taxrate, PickedQuantity,LastEditedBy) +SELECT TOP 1 OrderID, StockItemID, Description, PackageTypeID = 0, Quantity, UnitPrice, taxrate , PickedQuantity,LastEditedBy +FROM Sales.OrderLines; + +DROP INDEX IF EXISTS [NCCX_Sales_OrderLines] ON [Sales].[OrderLines] -/****** Object: Index [NCCX_Sales_OrderLines] Script Date: 4/20/2017 11:27:27 AM ******/ CREATE NONCLUSTERED COLUMNSTORE INDEX [NCCX_Sales_OrderLines] ON [Sales].[OrderLines] ( [OrderID], @@ -10,72 +14,74 @@ CREATE NONCLUSTERED COLUMNSTORE INDEX [NCCX_Sales_OrderLines] ON [Sales].[OrderL [UnitPrice], [PickedQuantity], [PackageTypeID] -- adding package type id for demo purpose -)WITH (DROP_EXISTING = OFF, COMPRESSION_DELAY = 0) ON [USERDATA] +)WITH (DROP_EXISTING = OFF, COMPRESSION_DELAY = 0) GO CREATE OR ALTER PROCEDURE [dbo].[initialize] -as begin +AS BEGIN -ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; -ALTER DATABASE current SET QUERY_STORE CLEAR ALL; -ALTER DATABASE current SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = OFF); + ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; + ALTER DATABASE current SET QUERY_STORE CLEAR ALL; -end +END GO CREATE OR ALTER PROCEDURE [dbo].[report] (@packagetypeid int) -as begin +AS BEGIN -select avg([UnitPrice]*[Quantity]) -from Sales.OrderLines -where PackageTypeID = @packagetypeid +EXEC sp_executesql N'select avg([UnitPrice]*[Quantity]) + from Sales.OrderLines + where PackageTypeID = @packagetypeid', N'@packagetypeid int', @packagetypeid; -end +END GO CREATE OR ALTER PROCEDURE [dbo].[regression] -as begin +AS BEGIN ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; -begin - declare @packagetypeid int = 1; +BEGIN + declare @packagetypeid int = 0; exec report @packagetypeid; -end +END -end +END GO CREATE OR ALTER PROCEDURE [dbo].[auto_tuning_on] -as begin +AS BEGIN -ALTER DATABASE current SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = ON); -ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; -ALTER DATABASE current SET QUERY_STORE CLEAR ALL; + ALTER DATABASE current SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = ON); + ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; + ALTER DATABASE current SET QUERY_STORE CLEAR ALL; -end +END GO CREATE OR ALTER PROCEDURE [dbo].[auto_tuning_off] -as begin +AS BEGIN -ALTER DATABASE current SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = OFF); -ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; -ALTER DATABASE current SET QUERY_STORE CLEAR ALL; + ALTER DATABASE current SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = OFF); + ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE; + ALTER DATABASE current SET QUERY_STORE CLEAR ALL; -end +END GO +/* -CREATE EVENT SESSION [APC - plans that are not corrected] ON SERVER - +CREATE EVENT SESSION [APC - plans that are not corrected] ON DATABASE ADD EVENT qds.automatic_tuning_plan_regression_detection_check_completed( WHERE ((([is_regression_detected]=(1)) AND ([is_regression_corrected]=(0))) AND ([option_id]=(1)))) -ADD TARGET package0.event_file(SET filename=N'plans_that_are_not_corrected') +-- Use file target only on SQL Server 2017: +-- ADD TARGET package0.event_file(SET filename=N'plans_that_are_not_corrected') +ADD TARGET package0.ring_buffer (SET max_memory = 1000) WITH (STARTUP_STATE=ON); GO -ALTER EVENT SESSION [APC - plans that are not corrected] ON SERVER STATE = start; \ No newline at end of file +ALTER EVENT SESSION [APC - plans that are not corrected] ON SERVER STATE = start; +*/ \ No newline at end of file diff --git a/samples/features/automatic-tuning/force-last-good-plan/wwwroot/index.html b/samples/features/automatic-tuning/force-last-good-plan/wwwroot/index.html index fdfeafc5..10d8e3b9 100644 --- a/samples/features/automatic-tuning/force-last-good-plan/wwwroot/index.html +++ b/samples/features/automatic-tuning/force-last-good-plan/wwwroot/index.html @@ -37,27 +37,37 @@

Automatic tuning

-
+
-
- 0 requests per second. -
-
- +
+ 0 requests per second.
- +
+ +
+
+

T-SQL query:

+
+SELECT AVG( UnitPrice * Quantity )
+FROM Sales.OrderLines
+WHERE PackageTypeID = @packagetypeid;
+

Enable FORCE LAST GOOD PLAN:

+
+ALTER DATABASE current
+SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = ON);
+
@@ -66,7 +76,7 @@