6

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

SQL Server 版本:

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-内存优化?view=sql-server-ver15

CrudTest_TempTable 1000, 100, 100
go 1000

相对

CrudTest_memopt_hash 1000, 100, 100
go 1000

这个测试做什么?

  • 1000 个刀片
  • 100 次随机更新
  • 100 次随机删除

这会重复 1000 次。

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

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

开始执行循环

消息 3998,级别 16,状态 1,第 3
行在批处理结束时检测到不可提交事务。事务被回滚。

消息 701,级别 17,状态 103,过程 CrudTest_memopt_hash,第 16 行 [Batch Start Line 2]
资源池“默认”中的系统内存不足,无法运行此查询。

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

  • 哈希索引包括 Col1 和 SpidFilter

  • 在单个事务中做所有事情使它工作得更快(但是没有它运行会很好)

  • 我正在生成随机 id - 没有它,每次迭代的记录最终都在同一个桶中

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

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

对我来说,这些结果意味着内存优化表不能替换临时表。你有类似的结果还是我做错了什么?

使用临时表的代码是:

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 代码。我已经在空数据库上执行了测试。

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

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

我的最后一次运行看起来像这样(临时表是表中最快的,但我能够使用内存优化表变量实现最快的时间):

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
4

2 回答 2

6

恕我直言,OP 中的测试无法显示表格的优势,memory-optimized因为这些表格的最大优势在于它们是,lock-and-latch free这意味着您update//根本不接受允许对这些表格进行并发更改。insertdeletelockstables

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

另一个观察结果:hash index定义在 上table是错误的,因为您只搜索one column并且哈希索引是在 上定义的two columns。两列上的哈希索引意味着它hash function适用于两个参数,但您只搜索一列,因此hash index不能使用。

您是否认为通过使用 mem opt 表可以提高临时表的性能,还是仅用于限制 tempdb 上的 IO?

Memory-optimized tables不应该替代temporary tables,如前所述,您将在高度并发 OLTP的环境中看到利润,而您猜想temporary table仅对您的会话可见,根本没有并发。

消除闩锁和锁。所有 In-Memory OLTP 内部数据结构都是 无锁无锁的。In-Memory OLTP 使用新的多版本并发控制 (MVCC) 来提供事务一致性。从用户的角度来看,它的行为方式类似于常规的 SNAPSHOT 事务隔离级别;但是,它不使用引擎盖下的锁定。这种模式允许多个会话处理相同的数据而不会相互锁定和阻塞,并提高了系统的可扩展性,允许充分利用现代多 CPU/多核硬件

被引书籍:Dmitri Korotkevitch 的 Pro SQL Server Internals

您如何看待标题“通过使用内存优化实现更快的临时表和表变量”

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

  • A. 内存优化表变量的基础知识
  • B. 场景:替换全局 tempdb ##table
  • C. 场景:替换会话 tempdb #table

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

B. 更换global tempdb ##table。我根本不使用它们。

C. 更换session tempdb #table。如前所述,session tempdb #table对任何其他会话都不可见,那么有什么好处呢?数据不进入磁盘?tempdb如果您真的遇到问题,您是否应该为您考虑最快的 SSD 磁盘tempdb?从 2014 年开始,disk即使在.bulk insertsRCSItempdb

于 2019-11-14T15:08:25.837 回答
0

可能不会看到性能改进,仅在非常特殊的应用程序中。SQL 过去曾涉足过“pin table”之类的东西,但优化器根据实际活动选择内存中的页面,几乎适用于所有情况。几十年来,这一直是性能调整。我认为“在记忆中”更像是一个营销接触点,而不是任何实际用途。请证明我错了。

于 2021-06-22T16:23:49.113 回答