Skip to content

Commit 501a733

Browse files
committed
Updated automatic plan corection demo
1 parent 59e69ef commit 501a733

File tree

5 files changed

+232
-105
lines changed

5 files changed

+232
-105
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp1.0</TargetFramework>
5+
<PreserveCompilationContext>true</PreserveCompilationContext>
6+
<AssemblyName>force-last-good-plan</AssemblyName>
7+
<OutputType>Exe</OutputType>
8+
<PackageId>force-last-good-plan</PackageId>
9+
<RuntimeFrameworkVersion>1.0.4</RuntimeFrameworkVersion>
10+
<PackageTargetFallback>$(PackageTargetFallback);dotnet5.6;portable-net45+win8</PackageTargetFallback>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<None Update="wwwroot\**\*">
15+
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
16+
</None>
17+
</ItemGroup>
18+
19+
<ItemGroup>
20+
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.0.3" />
21+
<PackageReference Include="Microsoft.AspNetCore.Routing" Version="1.0.3" />
22+
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.0.2" />
23+
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.0.3" />
24+
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.0.2" />
25+
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="1.0.2" />
26+
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.0.2" />
27+
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.0.2" />
28+
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.0.2" />
29+
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.0.2" />
30+
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.0.2" />
31+
<PackageReference Include="System.Data.SqlClient" Version="4.3.0" />
32+
<PackageReference Include="Belgrade.Sql.Client" Version="0.7.0" />
33+
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.1" />
34+
</ItemGroup>
35+
36+
</Project>

samples/features/automatic-tuning/force-last-good-plan/sql-scripts/demo-full.sql

Lines changed: 72 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,74 @@
11
/********************************************************
22
* SETUP - clear everything
33
********************************************************/
4-
EXEC [dbo].[initialize]
5-
4+
ALTER DATABASE current SET AUTOMATIC_TUNING (FORCE_LAST_GOOD_PLAN = OFF);
5+
EXEC dbo.initialize;
66

77
/********************************************************
88
* PART I
9-
* Plan regression identification.
9+
* Plan regression identification & manual tuning
1010
********************************************************/
1111

12-
-- 1. Start workload - execute procedure 30-300 times:
13-
begin
14-
declare @packagetypeid int = 7;
15-
exec dbo.report @packagetypeid
16-
end
17-
go 300
18-
-- Queries should be fast
19-
-- Optionally, include "Actual execution plan" in SSMS and show the plan (it should have Hash Aggregate)
12+
-- 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
13+
EXEC sp_executesql N'select avg([UnitPrice]*[Quantity])
14+
from Sales.OrderLines
15+
where PackageTypeID = @packagetypeid', N'@packagetypeid int', @packagetypeid = 7;
16+
GO 60
17+
-- 1. Execute this query 45-300 times to setup the baseline.
18+
-- If you have QUERY_STORE CAPTURE_POLICY=AUTO increase number in GO <number> to at least 60
2019

2120

22-
-- 2. Execute procedure that causes plan regression
23-
-- Optionally, include "Actual execution plan" in SSMS and show the plan (it should have Stream Aggregate)
24-
exec dbo.regression
21+
-- 2. Execute the procedure that causes plan regression
22+
-- Optionally, include "Actual execution plan" in SSMS and show the plan - it should have Stream Aggregate, Index Seek & Nested Loops
23+
EXEC dbo.regression;
2524

2625

27-
-- 3. Start workload again - verify that is slower.
28-
begin
29-
declare @packagetypeid int = 7;
30-
exec dbo.report @packagetypeid
31-
end
26+
-- 3. Start the workload again - verify that is slower.
27+
EXEC sp_executesql N'select avg([UnitPrice]*[Quantity])
28+
from Sales.OrderLines
29+
where PackageTypeID = @packagetypeid', N'@packagetypeid int', @packagetypeid = 7;
3230
go 20
33-
-- Optionally, include "Actual execution plan" in SSMS and show the plan (it should have Stream Aggregate)
31+
-- Optionally, include "Actual execution plan" in SSMS and show the plan - it should have Stream Aggregate with Non-clustered index seek.
3432

35-
-- 4. Find recommendation recommended by database:
36-
SELECT planForceDetails.query_id, reason, score,
37-
JSON_VALUE(details, '$.implementationDetails.script') [correction script],
38-
planForceDetails.[new plan_id], planForceDetails.[recommended plan_id]
39-
FROM sys.dm_db_tuning_recommendations
40-
CROSS APPLY OPENJSON (Details, '$.planForceDetails')
41-
WITH ( [query_id] int '$.queryId',
42-
[new plan_id] int '$.regressedPlanId',
43-
[recommended plan_id] int '$.recommendedPlanId'
44-
) as planForceDetails;
33+
-- 4. Find a recommendation that can fix this issue:
34+
SELECT reason, score,
35+
script = JSON_VALUE(details, '$.implementationDetails.script')
36+
FROM sys.dm_db_tuning_recommendations;
4537

38+
-- 4.1. Optionally get more detailed information about the regression and recommendation.
39+
SELECT reason, score,
40+
script = JSON_VALUE(details, '$.implementationDetails.script'),
41+
planForceDetails.[query_id],
42+
planForceDetails.[new plan_id],
43+
planForceDetails.[recommended plan_id],
44+
estimated_gain = (regressedPlanExecutionCount+recommendedPlanExecutionCount)*(regressedPlanCpuTimeAverage-recommendedPlanCpuTimeAverage)/1000000,
45+
error_prone = IIF(regressedPlanErrorCount>recommendedPlanErrorCount, 'YES','NO')
46+
FROM sys.dm_db_tuning_recommendations
47+
CROSS APPLY OPENJSON (Details, '$.planForceDetails')
48+
WITH ( [query_id] int '$.queryId',
49+
[new plan_id] int '$.regressedPlanId',
50+
[recommended plan_id] int '$.recommendedPlanId',
51+
regressedPlanErrorCount int,
52+
recommendedPlanErrorCount int,
53+
regressedPlanExecutionCount int,
54+
regressedPlanCpuTimeAverage float,
55+
recommendedPlanExecutionCount int,
56+
recommendedPlanCpuTimeAverage float ) as planForceDetails;
57+
-- IMPORTANT NOTE: check is estimated_gain > 10.
58+
-- If estimated_gain < 10 THEN FLGP=ON will not automatically force the plan!!!
59+
-- In that case increase the number of executions in initial workload.
60+
-- Make sure that SQL Engine uses columnstore in original plan and nonclustered index in regressed plan.
4661

4762
-- Note: User can apply script and force the recommended plan to correct the error.
4863
<<Insert T-SQL from the script column here and execute the script>>
4964
-- e.g.: exec sp_query_store_force_plan @query_id = 3, @plan_id = 1
5065

51-
-- 5. Start workload again - verify that is faster.
52-
begin
53-
declare @packagetypeid int = 7;
54-
exec dbo.report @packagetypeid
55-
end
56-
go 20
57-
-- Optionally, include "Actual execution plan" in SSMS and show the plan (it should have Hash Aggregate again)
66+
-- 5. Execute the query again - verify that it is faster.
67+
EXEC sp_executesql N'select avg([UnitPrice]*[Quantity])
68+
from Sales.OrderLines
69+
where PackageTypeID = @packagetypeid', N'@packagetypeid int', @packagetypeid = 7;
70+
GO 20
71+
-- Optionally, include "Actual execution plan" in SSMS and show the plan - it should have Hash Aggregate & Columnstore again
5872

5973

6074
-- In part II will be shown better approach - automatic tuning.
@@ -74,29 +88,26 @@ ALTER DATABASE current
7488
SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = ON);
7589

7690
-- Verify that actual state on FLGP is ON:
77-
SELECT name, desired_state_desc, actual_state_desc, reason_desc
78-
FROM sys.database_automatic_tuning_options;
79-
91+
SELECT name, actual_state_desc, status = IIF(desired_state_desc <> actual_state_desc, reason_desc, 'Status:OK')
92+
FROM sys.database_automatic_tuning_options
93+
WHERE name = 'FORCE_LAST_GOOD_PLAN';
8094

8195
-- 1. Start workload - execute procedure 30-300 times like in the phase I
82-
begin
83-
declare @packagetypeid int = 7;
84-
exec dbo.report @packagetypeid
85-
end
86-
go 300
96+
EXEC sp_executesql N'select avg([UnitPrice]*[Quantity])
97+
from Sales.OrderLines
98+
where PackageTypeID = @packagetypeid', N'@packagetypeid int', @packagetypeid = 7;
99+
GO 60
87100

88-
-- 2. Execute the procedure that causes plan regression
101+
-- 2. Execute the procedure that causes the plan regression
89102
exec dbo.regression;
90103

91-
-- 3. Start workload again - verify that it is slower.
92-
begin
93-
declare @packagetypeid int = 7;
94-
exec dbo.report @packagetypeid;
95-
end
96-
go 20
104+
-- 3. Start the workload again - verify that it is slower.
105+
EXEC sp_executesql N'select avg([UnitPrice]*[Quantity])
106+
from Sales.OrderLines
107+
where PackageTypeID = @packagetypeid', N'@packagetypeid int', @packagetypeid = 7;
108+
go 30
97109

98-
-- 4. Find recommendation that returns query perf regression
99-
-- and check is it in Verifying state:
110+
-- 4. Find a recommendation and check is it in "Verifying" or "Success" state:
100111
SELECT reason, score,
101112
JSON_VALUE(state, '$.currentValue') state,
102113
JSON_VALUE(state, '$.reason') state_transition_reason,
@@ -110,11 +121,11 @@ FROM sys.dm_db_tuning_recommendations
110121
) as planForceDetails;
111122

112123

113-
-- 5. Wait until recommendation is applied and start workload again - verify that it is faster.
114-
begin
115-
declare @packagetypeid int = 7;
116-
exec dbo.report @packagetypeid
117-
end
118-
go 30
124+
-- 5. Recommendation is in "Verifying" state, but the last good plan is forced, so the query will be faster:
125+
EXEC sp_executesql N'select avg([UnitPrice]*[Quantity])
126+
from Sales.OrderLines
127+
where PackageTypeID = @packagetypeid', N'@packagetypeid int', @packagetypeid = 7;
128+
129+
130+
-- Open Query Store/"Top Resource Consuming Queries" dialog in SSMS and show that the better plan is forced.
119131

120-
-- Open Query Store/"Top Resource Consuming Queries" dialog in SSMS and show that better plan is forced.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/***************************************************************************
2+
* Run this script on a empty database if you don't have WWI database and
3+
* you want to use new database instead of full WWI
4+
* If you are using SSMS, use Ctrl+Shift+M to populate parameters.
5+
***************************************************************************/
6+
ALTER DATABASE <database_name, sysname, flgp> MODIFY (EDITION = 'Premium', SERVICE_OBJECTIVE = '<azuredb_service_objective, varchar(6), P4>');
7+
SELECT DATABASEPROPERTYEX('<database_name, sysname, flgp>', 'ServiceObjective');
8+
-- Create minimal WWI schema required to run the sample:
9+
DROP TABLE IF EXISTS [Sales].[OrderLines];
10+
GO
11+
DROP SEQUENCE IF EXISTS [Sequences].[OrderLineID];
12+
GO
13+
DROP SCHEMA IF EXISTS [Sequences];
14+
GO
15+
DROP SCHEMA IF EXISTS [Sequences];
16+
GO
17+
18+
CREATE SCHEMA [Sequences];
19+
GO
20+
CREATE SEQUENCE [Sequences].[OrderLineID]
21+
AS [int]
22+
START WITH 231413
23+
INCREMENT BY 1
24+
MINVALUE -2147483648
25+
MAXVALUE 2147483647
26+
CACHE
27+
GO
28+
29+
CREATE TABLE [Sales].[OrderLines](
30+
[OrderLineID] [int] PRIMARY KEY,
31+
[OrderID] [int] NOT NULL,
32+
[StockItemID] [int] NOT NULL,
33+
[Description] [nvarchar](100) NOT NULL,
34+
[PackageTypeID] [int] NOT NULL,
35+
[Quantity] [int] NOT NULL,
36+
[UnitPrice] [decimal](18, 2) NULL,
37+
[TaxRate] [decimal](18, 3) NOT NULL,
38+
[PickedQuantity] [int] NOT NULL,
39+
[PickingCompletedWhen] [datetime2](7) NULL,
40+
[LastEditedBy] [int] NOT NULL,
41+
[LastEditedWhen] [datetime2](7) NOT NULL
42+
)
43+
GO
44+
ALTER TABLE [Sales].[OrderLines]
45+
ADD CONSTRAINT [DF_Sales_OrderLines_OrderLineID]
46+
DEFAULT (NEXT VALUE FOR [Sequences].[OrderLineID]) FOR [OrderLineID]
47+
GO
48+
ALTER TABLE [Sales].[OrderLines]
49+
ADD CONSTRAINT [DF_Sales_OrderLines_LastEditedWhen]
50+
DEFAULT (sysdatetime()) FOR [LastEditedWhen]
51+
GO
52+
DROP INDEX IF EXISTS [FK_Sales_OrderLines_PackageTypeID]
53+
ON [Sales].[OrderLines]
54+
55+
CREATE NONCLUSTERED INDEX [FK_Sales_OrderLines_PackageTypeID]
56+
ON [Sales].[OrderLines]([PackageTypeID] ASC)
57+
GO
58+
59+
60+
-- Export Sales.OrderLines from WWI database using bcp:
61+
-- bcp WideWorldImporters.Sales.OrderLines out OrderLines.dat -T -c -U <wwi_user_name, nvarchar(50), WWIUSERNAME> -P <wwi_password, nvarchar(50), WWIPASSWORD> -S <wwi server/instance, nvarchar(50), .//SQLEXPRESS>
62+
63+
-- Import data in new database using bcp:
64+
-- bcp <database_name, sysname, flgp>.Sales.OrderLines in OrderLines.dat -c -U <demo_user_name, nvarchar(50), DEMOUSERNAME> -P <demo_password, nvarchar(50), DEMOPASSWORD> -S <demo server/instance, nvarchar(50), .//SQLEXPRESS>
Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
DROP INDEX IF EXISTS [NCCX_Sales_OrderLines] ON [Sales].[OrderLines]
1+
-- Insert one OrderLine that with PackageTypeID=(0) will cause regression
2+
INSERT INTO Sales.OrderLines(OrderId, StockItemID, Description, PAckageTypeID, quantity, unitprice, taxrate, PickedQuantity,LastEditedBy)
3+
SELECT TOP 1 OrderID, StockItemID, Description, PackageTypeID = 0, Quantity, UnitPrice, taxrate , PickedQuantity,LastEditedBy
4+
FROM Sales.OrderLines;
5+
6+
DROP INDEX IF EXISTS [NCCX_Sales_OrderLines] ON [Sales].[OrderLines]
27

3-
/****** Object: Index [NCCX_Sales_OrderLines] Script Date: 4/20/2017 11:27:27 AM ******/
48
CREATE NONCLUSTERED COLUMNSTORE INDEX [NCCX_Sales_OrderLines] ON [Sales].[OrderLines]
59
(
610
[OrderID],
@@ -10,72 +14,74 @@ CREATE NONCLUSTERED COLUMNSTORE INDEX [NCCX_Sales_OrderLines] ON [Sales].[OrderL
1014
[UnitPrice],
1115
[PickedQuantity],
1216
[PackageTypeID] -- adding package type id for demo purpose
13-
)WITH (DROP_EXISTING = OFF, COMPRESSION_DELAY = 0) ON [USERDATA]
17+
)WITH (DROP_EXISTING = OFF, COMPRESSION_DELAY = 0)
1418
GO
1519

1620
CREATE OR ALTER PROCEDURE [dbo].[initialize]
17-
as begin
21+
AS BEGIN
1822

19-
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
20-
ALTER DATABASE current SET QUERY_STORE CLEAR ALL;
21-
ALTER DATABASE current SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = OFF);
23+
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
24+
ALTER DATABASE current SET QUERY_STORE CLEAR ALL;
2225

23-
end
26+
END
2427
GO
2528

2629

2730
CREATE OR ALTER PROCEDURE [dbo].[report] (@packagetypeid int)
28-
as begin
31+
AS BEGIN
2932

30-
select avg([UnitPrice]*[Quantity])
31-
from Sales.OrderLines
32-
where PackageTypeID = @packagetypeid
33+
EXEC sp_executesql N'select avg([UnitPrice]*[Quantity])
34+
from Sales.OrderLines
35+
where PackageTypeID = @packagetypeid', N'@packagetypeid int', @packagetypeid;
3336

34-
end
37+
END
3538
GO
3639

3740

3841
CREATE OR ALTER PROCEDURE [dbo].[regression]
39-
as begin
42+
AS BEGIN
4043

4144
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
42-
begin
43-
declare @packagetypeid int = 1;
45+
BEGIN
46+
declare @packagetypeid int = 0;
4447
exec report @packagetypeid;
45-
end
48+
END
4649

47-
end
50+
END
4851
GO
4952

5053
CREATE OR ALTER PROCEDURE [dbo].[auto_tuning_on]
51-
as begin
54+
AS BEGIN
5255

53-
ALTER DATABASE current SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = ON);
54-
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
55-
ALTER DATABASE current SET QUERY_STORE CLEAR ALL;
56+
ALTER DATABASE current SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = ON);
57+
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
58+
ALTER DATABASE current SET QUERY_STORE CLEAR ALL;
5659

57-
end
60+
END
5861
GO
5962

6063

6164
CREATE OR ALTER PROCEDURE [dbo].[auto_tuning_off]
62-
as begin
65+
AS BEGIN
6366

64-
ALTER DATABASE current SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = OFF);
65-
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
66-
ALTER DATABASE current SET QUERY_STORE CLEAR ALL;
67+
ALTER DATABASE current SET AUTOMATIC_TUNING ( FORCE_LAST_GOOD_PLAN = OFF);
68+
ALTER DATABASE SCOPED CONFIGURATION CLEAR PROCEDURE_CACHE;
69+
ALTER DATABASE current SET QUERY_STORE CLEAR ALL;
6770

68-
end
71+
END
6972
GO
73+
/*
7074
71-
CREATE EVENT SESSION [APC - plans that are not corrected] ON SERVER
72-
75+
CREATE EVENT SESSION [APC - plans that are not corrected] ON DATABASE
7376
ADD EVENT qds.automatic_tuning_plan_regression_detection_check_completed(
7477
WHERE ((([is_regression_detected]=(1))
7578
AND ([is_regression_corrected]=(0)))
7679
AND ([option_id]=(1))))
77-
ADD TARGET package0.event_file(SET filename=N'plans_that_are_not_corrected')
80+
-- Use file target only on SQL Server 2017:
81+
-- ADD TARGET package0.event_file(SET filename=N'plans_that_are_not_corrected')
82+
ADD TARGET package0.ring_buffer (SET max_memory = 1000)
7883
WITH (STARTUP_STATE=ON);
7984
GO
8085
81-
ALTER EVENT SESSION [APC - plans that are not corrected] ON SERVER STATE = start;
86+
ALTER EVENT SESSION [APC - plans that are not corrected] ON SERVER STATE = start;
87+
*/

0 commit comments

Comments
 (0)