I have a query of the following form:
IF EXISTS (
SELECT 1
FROM (
SELECT RowID, OETID
FROM @InMemoryTableTypeTable i
UNION
SELECT RowID, OETID
FROM @InMemoryTableTypeTable d
) AS t
WHERE NOT EXISTS (
SELECT 1
FROM dbo.MyTable m WITH(FORCESEEK, ROWLOCK, UPDLOCK)
WHERE (m.OETID = t.RowID)
AND (m.SRID = t.OETID)
AND (m.WTID = @WTID)
AND (m.Status <> 1)
AND (m.SRID > 0)
)
)
...
The definition of dbo.MyTable
is:
CREATE TABLE [dbo].[MyTable](
[ID] [bigint] IDENTITY(1,1) NOT NULL,
[RowGUID] [uniqueidentifier] ROWGUIDCOL NOT NULL,
[WTID] [bigint] NOT NULL,
[OETID] [int] NOT NULL,
[SRID] [bigint] NOT NULL,
[Status] [tinyint] NOT NULL,
CONSTRAINT [PK_MyTable] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE UNIQUE NONCLUSTERED INDEX [IDX] ON [dbo].[MyTable]
(
[WTID] ASC,
[OETID] ASC,
[SRID] ASC
)
INCLUDE([Status])
WHERE ([SRID]>(0))
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
GO
ALTER TABLE [dbo].[MyTable] ADD CONSTRAINT [DF_MyTable_RowGUID] DEFAULT (NEWID()) FOR [RowGUID]
GO
The definition of @InMemoryTableTypeTable
is
CREATE TYPE [dbo].[TableType] AS TABLE(
[ID] [bigint] NOT NULL,
[RowID] [int] NOT NULL,
[OETID] [int] NOT NULL,
PRIMARY KEY NONCLUSTERED
(
[ID] ASC
)
)
WITH ( MEMORY_OPTIMIZED = ON )
GO
The table MyTable
contains ~500k rows and has a unique filtered index that has:
WTID
,OETID
andSRID
as keys in that order- a filter where
SRID
> 0 Status
as an included column
This means that the EXISTS
statement is SARGable.
However, depending on how many records are in @InMemoryTableTypeTable
and what mood SQL Server seems to be in, sometimes the index seek will only seek on WTID
and push the rest of the predication up into the Left Anti Semi Join. If this happens and memory on the SQL Server itself is under pressure, the query can sit there for 20 minutes or so. For some values of @WTID
there could be 1 row or there could be 200k that have just been inserted earlier in the same session.
Here is the good plan: https://www.brentozar.com/pastetheplan/?id=H1-V_Jz7R
Here is the bad plan: https://www.brentozar.com/pastetheplan/?id=SJD-QZGQA
Is there a way to force SQL Server to apply the predication to all 3 columns in the Index Seek every time?
I have tried breaking this out of the IF and using the OPTIMIZE FOR UNKNOWN
and OPTIMIZE FOR (@WTID UNKNOWN)
hints to no avail.
The seek is more for concurrency: the reads and writes within that table for each session will be segregated by WTID. However, removing those table hints makes no difference, it always scans t and seeks into m, it's the position of the OETID and SRID predication that appears to make the difference.
This post Actual and Estimated rows differ greatly led me to the ASSUME_MIN_SELECTIVITY_FOR_FILTER_ESTIMATES
hint which produces the plan I want (most of the time) along with RECOMPILE
. Combining this with FORCE_LEGACY_CARDINALITY_ESTIMATION
reverts to the "wrong" plan.