You won't only see ENCRYPTION_SCAN resource in your wait list when Encryption (like TDE) is used.
Certain operations will take a shared lock on this resource to make sure the database is not being encrypted during the operation.
The moment you would encrypt a user database with TDE, the tempdb will also be encrypted (otherwise, you would have security risk when User data is used in temp db).
Therefore, some operations will take a shared lock on ENCRYPTION_SCAN in Tempdb to prevent Tempdb from getting encrypted.
Here are two examples:
BULK INSERT
IF object_id('tempdb..##NumberCreation') IS NOT NULL
drop table ##NumberCreation
GO
--create temp table to hold numbers
create table ##NumberCreation (C int NOT NULL);
GO
-- CREATE Numbers by using trick from Itzik -> http://sqlmag.com/sql-server/virtual-auxiliary-table-numbers
WITH L1 AS ( SELECT 1 as C UNION SELECT 0 ),
L2 AS ( SELECT 1 as C FROM L1 CROSS JOIN L1 as B ),
L3 AS ( SELECT 1 as C FROM L2 CROSS JOIN L2 as B ),
L4 AS ( SELECT 1 as C FROM L3 CROSS JOIN L3 as B ),
L5 AS ( SELECT 1 as C FROM L4 CROSS JOIN L4 as B ),
L6 AS ( SELECT 1 as C FROM L5 CROSS JOIN L5 as B),
Nums as (SELECT ROW_NUMBER() OVER (ORDER BY C) as C FROM L6)
insert ##NumberCreation(C)
SELECT TOP 500000 C
FROM Nums
The above code will generate 500k records in a global temp table, you can export these with the following commands. If you run this from SSMS, make sure you are in SQLCMD mode:
--Export
!!bcp ##NumberCreation out "E:\SQLServer\Backup\test\export.dat" -T -n
--format file
!!bcp ##NumberCreation format nul -T -n -f "E:\SQLServer\Backup\test\export.fmt"
Make sure to choose a directory where SQL Server service account has write permissions and if you run this from SSMS, run it locally on the SQL Server.
Next thing is to start a bulk insert loop. While the loop is running, open a second screen and start running sp_lock untill you see the ENCRYPTION_SCAN shared lock in DB_ID 2 (Which is Tempdb).
The bulk import loop:
BEGIN
IF OBJECT_ID('tempdb..#Import') IS NOT NULL
DROP TABLE #Import ;
CREATE TABLE #Import (C INT) ;
BULK INSERT #Import
FROM 'E:\SQLServer\Backup\test\export.dat' WITH (FORMATFILE='E:\SQLServer\Backup\test\export.fmt', FIRSTROW=1, TABLOCK) ;
END
GO 500 --run it 500 times
See the result of sp_lock in second window:
SORT IN TEMPDB
With the same Temp table in place start this very simple loop:
SELECT * from #Import order by C
go 50
It will produce the following Execution plan:
(Make sure that #Import is actually populated, since depending on when you stopped the previous bulk import loop, it could be empty!)
Again, run sp_lock in a second window until you see the ENCRYPTION_SCAN Resource popping up:
Now you know, why this resource wait is showing up. It could be very well that this is not your problem. I'd just wanted to point out the other reasons that make ENCRYPTION_SCAN show up. The reason for your query slowdown might be something else. I'll leave improving your query plan up to the query plan experts on this site ;-) However, could you post the actual execution plan as well instead of just the estimated plan?
Best Answer
Explanation of behavior
Some of the causes of the
IO_COMPLETION
wait type are:There are two sorts that could be spilling, which uses tempdb.
A source of the slowness could also be one the unfortunate "many to many merge join" in the middle of the execution plan (which also uses tempdb, although it doesn't cause
IO_COMPLETION
waits):The estimated plan shows ~2 GB of data coming out of that merge join - and that amount of data could be even higher if the estimates are off.
You mentioned the problem is intermittent, which could be because of tempdb contention (if other queries are running at the time).
Suggestions for improvement
Temp table rewrite
The best option I can think of would be to break the query up into smaller chunks. For instance, you could just select the
trans_header
rows that meet your where clause into a#temp
table. Then use that temp table in the main query instead oftrans_header
.This could improve estimates and let the optimizer produce a better plan - potentially avoiding any spills or other tempdb activity.
That, generally, looks like this. Sorry if there are any slight typos, it's tough not having intellisense ?
Fix implicit conversions
Speaking of estimates, you have several implicit conversion warnings. It's difficult to advise on how to deal with them, since we don't have table and index definitions, but you should review them to see if they can be avoided. Especially the ones in the
WHERE
clause:Join hint
One low-effort approach would be to try and avoid that specific merge join with a join hint. This might cause performance to get worse, though, because it will force the order of the joins as written, which limits the optimizer can do quite a bit:
Note that this wouldn't likely help with sort spills.