IF NOT EXISTS SELECT THEN INSERT 如何比 UNIQUE 索引更快?

use*_*547 7 index sql-server

在 SQL Server 中如何...

斯普:

CREATE PROCEDURE insertToTable
    @field1 VARCHAR(256), @field2 varchar(256), @field3 varchar(256)
AS
BEGIN
    SET NOCOUNT ON

    IF NOT EXISTS (SELECT * FROM my_table WHERE field1 = @field1)
      INSERT INTO my_table
        (field1, field2, field3)
      VALUES (@field1, @field2, @field3);
    ELSE
      THROW 50000, 'xxxxxx', 1;
    END
GO
Run Code Online (Sandbox Code Playgroud)

桌子:

CREATE TABLE my_table (
    field1 VARCHAR(256) NOT NULL,
    field2 VARCHAR(256) NOT NULL,
    field3 VARCHAR(256) NOT NULL
);
CREATE INDEX idx_field1 ON my_table(field1);
Run Code Online (Sandbox Code Playgroud)

上面的比下面的快吗?

斯普:

CREATE PROCEDURE insertToTable
    @field1 VARCHAR(256), @field2 varchar(256), @field3 varchar(256)
AS
BEGIN
    SET NOCOUNT ON

    INSERT INTO my_table
        (field1, field2, field3)
    VALUES (@field1, @field2, @field3);
GO
Run Code Online (Sandbox Code Playgroud)

桌子:

CREATE TABLE my_table (
    field1 VARCHAR(256) NOT NULL,
    field2 VARCHAR(256) NOT NULL,
    field3 VARCHAR(256) NOT NULL
);
CREATE UNIQUE INDEX idx_field1 ON my_table(field1);
Run Code Online (Sandbox Code Playgroud)

样本输入:

字段 1:F56yCgZ9AEm9aFpTyjwhERtqNeglYEow

字段2:BD84CE2A514316164B7448C804B178AD8F6F597E8EC6F25F4D6E36287259C65F67E7206E82A4F8EFD2389C0821C0C70E2D7A5C6B16D7A5C5C5A5C5D6D6C5A0C5C5D6C5C5A0D16A0C75C5C5A5C6D10A5

字段 3:A18E9049117A77E6A4D41C6CA3FFDEA65D842BF1F57705405B4E66969531D93D

输入是由 Web 应用程序并使用准备好的语句即时生成的。我使用 Jmeter 生成对我的 Web 应用程序的请求。

使用UNIQUE索引,在 100K 次插入后插入性能会下降并变得更糟。

使用NON UNIQUE索引和手动检查IF NOT EXISTS SELECT,即使插入了数百万条记录,性能也保持不变。

这些值足够独特,不会生成重复的值。即使在插入了几百万个值之后。

pac*_*ely 5

最终更新:

正是 INSERT 真正减慢了速度。

当唯一索引就位时,添加的每条新记录 SQL 都必须检查该值是否已经存在。随着表格的增长,交叉引用的数量也会增加。非唯一索引不需要交叉引用,因此性能是恒定的。

对于 SELECT 语句,唯一索引通常更快,但在更新表时需要付出代价。

以下是为什么 SELECT 有时在唯一索引上会变慢的原因

我已经部分地重新创建了您的情况,我认为这归结为参数嗅探和 SQL 更喜欢在 HEAP 上使用非唯一索引的组合。

设置2个测试表,其中一个是堆(就像你的表一样)。

CREATE TABLE dbo.TEST1(ID VARCHAR(255) NOT NULL,TXT1 VARCHAR(255) NOT NULL,TXT2 VARCHAR(255) NOT NULL)
CREATE TABLE dbo.TEST2(ID VARCHAR(255) NOT NULL,TXT1 VARCHAR(255) NOT NULL,TXT2 VARCHAR(255) NOT NULL)
GO
INSERT INTO dbo.TEST1 VALUES(NEWID(),NEWID(),NEWID())
GO 30000

INSERT INTO dbo.TEST2
SELECT * FROM dbo.TEST1
GO

CREATE CLUSTERED INDEX cidx ON dbo.TEST1 (ID)
CREATE INDEX idx_nu ON dbo.TEST1 (ID)
CREATE UNIQUE INDEX idx_u ON dbo.TEST1 (ID)

CREATE INDEX idx_nu ON dbo.TEST2 (ID)
CREATE UNIQUE INDEX idx_u ON dbo.TEST2 (ID)
Run Code Online (Sandbox Code Playgroud)

查看索引的占用空间,在 HEAP 上,UNIQUE 索引的占用空间比 NON-UNIQUE 索引小。(也许非唯一索引的页面包含额外的 - 可能有用 - 信息)(注意:多次运行上述代码后,页面计数没有差异,可能是由于缓存,更改“GO 30000”以纠正问题。)

SELECT
    s.name AS SchemaName,
    t.name AS TableName,
    i.name AS IndexName,
    p.row_count,
    SUM (p.used_page_count) as used_pages_count,
    SUM (CASE
            WHEN (i.index_id < 2) THEN (in_row_data_page_count + lob_used_page_count + row_overflow_used_page_count)
            ELSE lob_used_page_count + row_overflow_used_page_count
        END) as pages
FROM 
    sys.dm_db_partition_stats  AS p 
        JOIN sys.tables AS t 
            ON 
            p.object_id = t.object_id
        JOIN sys.indexes AS i 
            ON 
            i.[object_id] = t.[object_id] 
            AND 
            p.index_id = i.index_id
        JOIN sys.schemas AS s 
            ON
            t.schema_id = s.schema_id
WHERE
    t.name IN ('TEST1','TEST2')
GROUP BY 
    s.name
    ,t.name
    ,i.name
    ,p.row_count
Run Code Online (Sandbox Code Playgroud)

现在用文字和变量查询表。

--SCAN of the UNIQUE index
DECLARE @account_id VARCHAR(255) = (SELECT TOP 1 ID FROM dbo.TEST2 WHERE ID like '%A%') 

--Parameter Sniffing kicks in --The optimiser doesn't know the value of @account_id

--SEEK of the CLUSTERED index 
DECLARE @ID1 VARCHAR(255)  = (SELECT TOP 1 ID FROM dbo.TEST1 WHERE ID = @account_id)

--SEEK of the NON UNIQUE index
DECLARE @ID2 VARCHAR(255)  = (SELECT TOP 1 ID FROM dbo.TEST2 WHERE ID = @account_id)
Run Code Online (Sandbox Code Playgroud)

出于某种原因,SQL 在执行 SEEK 操作时更喜欢 HEAP 上的 NON UNIQUE 索引。

这就是我认为正在发生的事情。当 Non-Unique 索引有更多的 Pages 时,STATS 中对应的 Histogram 有更多的 STEPS,运行下面的代码。

DBCC SHOW_STATISTICS ( 'TEST2' , 'idx_nu' )
DBCC SHOW_STATISTICS ( 'TEST2' , 'idx_u' )
Run Code Online (Sandbox Code Playgroud)

额外的 STEPS 创建底层索引的更细粒度的视图,因此优化器(知道 EQ_ROWS 始终为 1)从非唯一索引中获得更好的基数估计。