Sample Header Ad - 728x90

SLEEP_TASK waits on single-threaded UPDATE that does not generate an in-flight plan, how are these waits possible?

1 vote
0 answers
92 views
Yesterday, I witnesses a running query in sp_WhoIsActive that I could not explain. It had the following properties: * It ran in a nested transaction. The first transaction bulk inserts into a temp table that is later given a unique clustered index. The second runs an UPDATE against a system-versioned table. * The UPDATE was running for hours despite usually taking less than seconds. 5 minutes at most. * There were multiple calls to this UPDATE in different sessions, all being blocked by one of them. * sp_WhoIsActive revealed that: * The query has no in-flight execution plan (@get_plans = 2) * **The block leader is almost always blocked on "SLEEP_TASK", but sometimes it isn't blocked at all. I recall that it seems to stay blocked on this for about 4ms.** * The query was still doing work, only reads as far as I could tell, but incredible slowly (from @delta_interval = 10) * My guess is that the query waits on SLEEP_TASK for some set amount of time, does one read, and then goes back to sleep. * All of the reads were logical, not physical. * The block leading query had so far done millions of reads, but the blocked queries had only done tens of thousands * The query was single-threaded (@get_task_info = 2) * open_tran_count was 3 * I cannot share the XML showing the locks, but I find it interesting that it shows that the block leader shows: * S lock on "DATABASE.ENCRYPTION_SCAN" * Sch-S lock on "METADATA.PLAN_GUIDE_HASH" * Lots of locks on both the table and its history table * tempdb locks all presumably taken by the transaction populating the temp table. * For the blocked queries, the lock XML showed that they were all waiting on an IX lock on the clustered index of the history table. This index is partitioned and not unique. The history table has no other indexes. * The main table has only a primary key. It is not partitioned. * I have confirmed that the history table is free of corruption. The text of the query this, but with the names changed.
UPDATE
    MyTable
SET
    MyTable.FloatCol = TempTable.FloatCol,
    MyTable.Date = TempTable.Date
FROM
   #TempTableWithUniqueClusteredIndex AS TempTable
JOIN
    TemporalTableWithJustAPrimaryKey AS MyTable
ON
    MyTable.PrimaryKeyInt = TempTable.PrimaryKeyInt;
Query Store holds an estimated plan for this query. It is of this shape. Query plan shape With this database dropping and creating script, I can get an actual execution plan of identical shape.
SET STATISTICS XML OFF;
IF @@TRANCOUNT > 0
BEGIN
    ROLLBACK;
END;

-- =========================================
-- 1. Drop/Create sample DB and switch context
-- =========================================
USE master;
GO

ALTER DATABASE SampleDB SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
GO

DROP DATABASE IF EXISTS SampleDB;
GO

CREATE DATABASE SampleDB;
GO

USE SampleDB;
GO

-- =========================================
-- 2. Partition function & scheme for history
--    (partitioning by ValidFrom date/time)
-- =========================================
CREATE PARTITION FUNCTION PF_ValidFrom (DATETIME2) 
AS RANGE LEFT FOR VALUES
    ('2015-01-01','2020-01-01','2025-01-01');
GO

CREATE PARTITION SCHEME PS_ValidFrom 
AS PARTITION PF_ValidFrom
    ALL TO ([PRIMARY]);
GO

-- =========================================
-- 3. Pre-create history table on scheme
-- =========================================
CREATE TABLE dbo.MainTableHistory
(
    PrimaryKeyInt INT        NOT NULL,
    FloatCol      FLOAT      NOT NULL,
    [Date]        DATE       NOT NULL,
    ValidFrom     DATETIME2  NOT NULL,
    ValidTo       DATETIME2  NOT NULL
)
ON PS_ValidFrom (ValidFrom);
GO

-- Partitioned, non-unique clustered index on history
CREATE CLUSTERED INDEX CX_MainTableHistory
    ON dbo.MainTableHistory(PrimaryKeyInt, ValidFrom)
    ON PS_ValidFrom (ValidFrom);
GO

-- =========================================
-- 4. Create main temporal table (unpartitioned)
--    with a DATE column and system-period cols
-- =========================================
CREATE TABLE dbo.TemporalTableWithJustAPrimaryKey
(
    PrimaryKeyInt INT         NOT NULL
      CONSTRAINT PK_TemporalTable PRIMARY KEY,
    FloatCol       FLOAT       NOT NULL,
    [Date]         DATE        NOT NULL,
    ValidFrom      DATETIME2   GENERATED ALWAYS AS ROW START NOT NULL,
    ValidTo        DATETIME2   GENERATED ALWAYS AS ROW END   NOT NULL,
    PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo)
)
ON [PRIMARY];
GO

-- =========================================
-- 5. Seed MainTable with 1,000 rows
-- =========================================
INSERT INTO dbo.TemporalTableWithJustAPrimaryKey (PrimaryKeyInt, FloatCol, [Date])
SELECT TOP (1000)
       ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
       RAND(CHECKSUM(NEWID())) * 100,                -- random float
       DATEADD(day, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) % 365, '2020-01-01')
FROM sys.all_columns a
CROSS JOIN sys.all_columns b;
GO

-- =========================================
-- 6. Turn on system versioning pointing at our history
-- =========================================
ALTER TABLE dbo.TemporalTableWithJustAPrimaryKey
    SET (SYSTEM_VERSIONING = ON 
         (HISTORY_TABLE = dbo.MainTableHistory
         ,DATA_CONSISTENCY_CHECK = ON));
GO

-- =========================================
-- 7. Simulate nested transactions:
-- =========================================
BEGIN TRAN
    CREATE TABLE #TempTableWithUniqueClusteredIndex
    (
        PrimaryKeyInt INT   NOT NULL,
        FloatCol       FLOAT NOT NULL,
        [Date]         DATE  NOT NULL
    );

    INSERT INTO #TempTableWithUniqueClusteredIndex (PrimaryKeyInt, FloatCol, [Date])
    SELECT TOP (50)
           ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
           RAND(CHECKSUM(NEWID())) * 100,
           DATEADD(day, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) % 365, '2021-01-01')
    FROM sys.all_columns a
    CROSS JOIN sys.all_columns b;

    CREATE UNIQUE CLUSTERED INDEX CX_Temp_UC
      ON #TempTableWithUniqueClusteredIndex(PrimaryKeyInt);

BEGIN TRAN
    SET STATISTICS XML ON;

    UPDATE
        MyTable
    SET
        MyTable.FloatCol = TempTable.FloatCol,
        MyTable.Date = TempTable.Date
    FROM
       #TempTableWithUniqueClusteredIndex AS TempTable
    JOIN
        TemporalTableWithJustAPrimaryKey AS MyTable
    ON
        MyTable.PrimaryKeyInt = TempTable.PrimaryKeyInt;

ROLLBACK;
My question is this: **does anything that I have said above explain these SLEEP_TASK waits?** To [my knowledge](https://www.sqlskills.com/help/waits/sleep_task/) , such waits should be very rare on single-threaded queries.
Asked by J. Mini (1225 rep)
Jun 29, 2025, 01:02 AM
Last activity: Jul 2, 2025, 07:27 PM