SQL 服务器内存优化 Table - 与临时内存相比性能较差 table

SQL Server Memory Optimized Table - poor performance compared to temporary table

我正在尝试使用经典临时 tables 在 Microsoft SQL Server 2016 中对内存优化 tables 进行基准测试。

SQL 服务器版本:

Microsoft SQL Server 2016 (SP2) (KB4052908) - 13.0.5026.0 (X64)  Mar 18 2018 09:11:49   
Copyright (c) Microsoft Corporation  
Developer Edition (64-bit) on Windows 10 Enterprise 10.0 <X64> (Build 17134: ) (Hypervisor) 

我正在执行此处描述的步骤:https://docs.microsoft.com/en-us/sql/relational-databases/in-memory-oltp/faster-temp-table-and-table-variable-by-using-memory-optimization?view=sql-server-ver15

CrudTest_TempTable 1000, 100, 100
go 1000

对比

CrudTest_memopt_hash 1000, 100, 100
go 1000

这个测试是做什么的?

重复 1000 次。

第一个使用经典临时 tables 的存储过程需要大约 6 秒才能 运行。

第二个存储过程至少需要 15 秒并且通常会出错:

Beginning execution loop

Msg 3998, Level 16, State 1, Line 3
Uncommittable transaction is detected at the end of the batch. The transaction is rolled back.

Msg 701, Level 17, State 103, Procedure CrudTest_memopt_hash, Line 16 [Batch Start Line 2]
There is insufficient system memory in resource pool 'default' to run this query.

我做了以下优化(在它变得更糟之前):

我还没有创建本地编译的 SP,因为我的结果很糟糕。

我的盒子上有足够的空闲 RAM,SQL 服务器可以使用它 - 在不同的情况下它会分配很多内存,但在这个测试用例中它只会出错。

对我而言,这些结果意味着内存优化 tables 无法替代临时 tables。你有类似的结果还是我做错了什么?

使用临时 tables 的代码是:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

DROP PROCEDURE IF EXISTS CrudTest_TempTable;
GO

CREATE PROCEDURE CrudTest_TempTable 
    @InsertsCount INT, @UpdatesCount INT, @DeletesCount INT
AS
BEGIN
    SET NOCOUNT ON;
    BEGIN TRAN;

    CREATE TABLE #tempTable  
    (  
        Col1 INT NOT NULL PRIMARY KEY CLUSTERED,  
        Col2 NVARCHAR(4000),
        Col3 NVARCHAR(4000),
        Col4 DATETIME2,
        Col5 INT NOT NULL
    ); 

    DECLARE @cnt INT = 0;
    DECLARE @currDate DATETIME2 = GETDATE();


    WHILE @cnt < @InsertsCount
    BEGIN
        INSERT INTO #tempTable (Col1, Col2, Col3, Col4, Col5)
        VALUES (@cnt, 
        'sdkfjsdjfksjvnvsanlknc kcsmksmk ms mvskldamvks mv kv al kvmsdklmsdkl mal mklasdmf kamfksam kfmasdk mfksamdfksafeowa fpmsad lak',
        'msfkjweojfijm  skmcksamepi  eisjfi ojsona npsejfeji a piejfijsidjfai  spfdjsidjfkjskdja kfjsdp fiejfisjd pfjsdiafjisdjfipjsdi s dfipjaiesjfijeasifjdskjksjdja sidjf pajfiaj pfsdj pidfe',
        @currDate, 100);

        SET @cnt = @cnt + 1;
    END
    SET @cnt = 0;

    WHILE @cnt < @UpdatesCount
    BEGIN
        UPDATE #tempTable SET Col5 = 101 WHERE Col1 = cast ((rand() * @InsertsCount) as int);

        SET @cnt = @cnt + 1;
    END
    SET @cnt = 0;

    WHILE @cnt < @DeletesCount
    BEGIN
        DELETE FROM #tempTable WHERE Col1 = cast ((rand() * @InsertsCount) as int);

        SET @cnt = @cnt + 1;
    END

    COMMIT;
END
GO

内存测试中使用的对象是:

DROP PROCEDURE IF EXISTS CrudTest_memopt_hash;
GO

DROP SECURITY POLICY IF EXISTS tempTable_memopt_hash_SpidFilter_Policy;
GO

DROP TABLE IF EXISTS tempTable_memopt_hash;
GO

DROP FUNCTION IF EXISTS fn_SpidFilter;
GO

CREATE FUNCTION fn_SpidFilter(@SpidFilter smallint)  
    RETURNS TABLE  
    WITH SCHEMABINDING , NATIVE_COMPILATION  
AS  
    RETURN  
        SELECT 1 AS fn_SpidFilter  
            WHERE @SpidFilter = @@spid;
GO

CREATE TABLE tempTable_memopt_hash  
(  
    Col1 INT NOT NULL,
    Col2 NVARCHAR(4000),
    Col3 NVARCHAR(4000),
    Col4 DATETIME2,
    Col5 INT NOT NULL,

    SpidFilter  SMALLINT    NOT NULL   DEFAULT (@@spid),  
    INDEX ix_SpidFiler NONCLUSTERED (SpidFilter), 
    INDEX ix_hash HASH (Col1, SpidFilter) WITH (BUCKET_COUNT=100000),
    CONSTRAINT CHK_SpidFilter CHECK ( SpidFilter = @@spid )
) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_ONLY);
GO


CREATE SECURITY POLICY tempTable_memopt_hash_SpidFilter_Policy  
    ADD FILTER PREDICATE dbo.fn_SpidFilter(SpidFilter)  
    ON dbo.tempTable_memopt_hash  
    WITH (STATE = ON); 
GO

使用它们的存储过程是:

CREATE PROCEDURE CrudTest_memopt_hash 
    @InsertsCount INT, @UpdatesCount INT, @DeletesCount int
AS
BEGIN

    SET NOCOUNT ON;
    BEGIN TRAN;

    DECLARE @cnt INT = 0;
    DECLARE @currDate DATETIME2 = GETDATE();

    DECLARE @IdxStart INT = CAST ((rand() * 1000) AS INT);

    WHILE @cnt < @InsertsCount
    BEGIN
        INSERT INTO tempTable_memopt_hash(Col1, Col2, Col3, Col4, Col5)
        VALUES (@IdxStart + @cnt, 
        'sdkfjsdjfksjvnvsanlknc kcsmksmk ms mvskldamvks mv kv al kvmsdklmsdkl mal mklasdmf kamfksam kfmasdk mfksamdfksafeowa fpmsad lak',
        'msfkjweojfijm  skmcksamepi  eisjfi ojsona npsejfeji a piejfijsidjfai  spfdjsidjfkjskdja kfjsdp fiejfisjd pfjsdiafjisdjfipjsdi s dfipjaiesjfijeasifjdskjksjdja sidjf pajfiaj pfsdj pidfe',
        @currDate, 100);

        SET @cnt = @cnt + 1;
    END
    SET @cnt = 0;

    WHILE @cnt < @UpdatesCount
    BEGIN
        UPDATE tempTable_memopt_hash 
        SET Col5 = 101 
        WHERE Col1 = @IdxStart + cast ((rand() * @InsertsCount) as int);

        SET @cnt = @cnt + 1;
    END
    SET @cnt = 0;

    WHILE @cnt < @DeletesCount
    BEGIN
        DELETE FROM tempTable_memopt_hash 
        WHERE Col1 = @IdxStart + cast ((rand() * @InsertsCount) as int);

        SET @cnt = @cnt + 1;
    END

    DELETE FROM tempTable_memopt_hash;
    COMMIT;
END
GO

指数统计:

table   index   total_bucket_count  empty_bucket_count  empty_bucket_percent    avg_chain_length    max_chain_length
[dbo].[tempTable_memopt_hash]   PK__tempTabl__3ED0478731BB5AF0  131072  130076  99  1   3

更新

我包括我的最终测试用例和 sql 用于创建过程的代码,tables 等。我在空数据库上执行了测试。

SQL代码:https://pastebin.com/9K6SgAqZ

测试用例:https://pastebin.com/ckSTnVqA

我的最后一个 运行 看起来像这样(温度 table 是最快的 tables,但我能够使用内存优化实现最快的时间 table变量):

Start CrudTest_TempTable 2019-11-18 10:45:02.983
Beginning execution loop
Batch execution completed 1000 times.
Finish CrudTest_TempTable 2019-11-18 10:45:09.537
Start CrudTest_SpidFilter_memopt_hash 2019-11-18 10:45:09.537
Beginning execution loop
Batch execution completed 1000 times.
Finish CrudTest_SpidFilter_memopt_hash 2019-11-18 10:45:27.747
Start CrudTest_memopt_hash 2019-11-18 10:45:27.747
Beginning execution loop
Batch execution completed 1000 times.
Finish CrudTest_memopt_hash 2019-11-18 10:45:46.100
Start CrudTest_tableVar 2019-11-18 10:45:46.100
Beginning execution loop
Batch execution completed 1000 times.
Finish CrudTest_tableVar 2019-11-18 10:45:47.497

恕我直言,OP中的测试无法显示memory-optimized tables的优势 因为这些table的最大优点是它们是lock-and-latch free,这意味着你的update/insert/delete不带locks完全允许对这些 tables.

进行并发更改

但是所做的测试根本不包括并发更改,显示的代码将所有更改集中在一个 session

另一个观察结果:table 上定义的 hash index 是错误的,因为您仅在 one column 上搜索,而散列索引是在 two columns 上定义的。两列上的哈希索引意味着 hash function 应用于两个参数,但您仅在一列上搜索,因此 hash index 无法使用。

Do you think by using mem opt tables I can get performance improvements over temp tables or is it just for limiting IO on tempdb?

Memory-optimized tables不应该替代temporary tables,前面已经说过,你会在高并发OLTP环境下看到收益,虽然正如您猜测 temporary table 仅对您的会话可见,但根本没有并发性。

Eliminate latches and locks. All In-Memory OLTP internal data structures are latch- and lock-free. In-Memory OLTP uses a new multi-version concurrency control (MVCC) to provide transaction consistency. From a user standpoint, it behaves in a way similar to the regular SNAPSHOT transaction isolation level; however, it does not use locking under the hood. This schema allows multiple sessions to work with the same data without locking and blocking each other and improves the scalability of the system allowing fully utilize modern multi-CPU/multi-core hardware.

引用书籍:Pro SQL 服务器内部,作者 Dmitri Korotkevitch

What do you think about the title "Faster temp table and table variable by using memory optimization"

我打开这篇文章并看到了这些示例(按照它们在文章中的顺序)

  • 一个。内存优化基础 table 变量
  • 乙。场景:替换全局tempdb ##table
  • C。场景:替换session tempdb #table

一个。我只在它们包含很少行的情况下才使用 table variables。我为什么要关心这几行?

乙。替换 global tempdb ##table 。我只是根本不使用它们。

摄氏度。替换 session tempdb #table。如前所述,session tempdb #table 对任何其他会话都不可见,那么有什么好处呢?数据不去磁盘?如果 tempdb 确实有问题,您是否应该为 tempdb 考虑最快的 SSD 磁盘?从 2014 年开始,tempdb 对象不一定会转到 disk,即使在 bulk inserts 的情况下,无论如何我什至在我的数据库上启用了 RCSI 并且 [=32= 没有问题].

可能不会看到性能提升,只有在非常特殊的应用程序中才会看到。 SQL 过去曾涉足 'pin table' 之类的东西,但优化器根据实际 activity 选择内存中的页面,可能在几乎所有情况下都表现出色。几十年来,这一直在进行性能调整。我认为 'in memory' 比任何实际用途更像是一个营销接触点。请证明我错了。