我正在测试一些索引,并想对哪些索引执行得更好进行一些 AB 测试(主要是索引中字段的顺序和指定的其他字段)。
我有一个正在测试的数据库的备份,但该备份需要很长时间才能恢复。我更愿意只创建索引 foo,收集性能指标,然后删除 foo 以将数据库返回到索引前状态并创建索引 bar。这是创建索引的工作方式,还是创建索引会以某种删除索引无法撤消的方式更改表?
我正在测试一些索引,并想对哪些索引执行得更好进行一些 AB 测试(主要是索引中字段的顺序和指定的其他字段)。
我有一个正在测试的数据库的备份,但该备份需要很长时间才能恢复。我更愿意只创建索引 foo,收集性能指标,然后删除 foo 以将数据库返回到索引前状态并创建索引 bar。这是创建索引的工作方式,还是创建索引会以某种删除索引无法撤消的方式更改表?
当使用包含一组已定义列的 SELECT 语句查询数据库时,大约 21 秒内会收到结果。
, *
如果在定义的列集列表末尾有一个额外的星号 ( ),则查询将在 2 秒内返回结果。
执行计划有很大不同。
您可以使用 PasteThePlan 中的链接找到好的实际查询执行计划和坏的实际查询执行计划。
SELECT -- DISTINCT -- 27.04.2020
'SchuelerKlasse' AS EcoQuery,
VX_PERSON.PER_MAN_ID, VX_PERSON.PER_ID, VX_PERSON.PER_NAME, VX_PERSON.PER_VORNAME, VX_PERSON.PER_LB_PER_ID,
VX_PERSON.PER_GESCHLECHT, VX_PERSON.PER_GEBURTSDATUM, VX_PERSON.PER_TELP, VX_PERSON.PER_MAILP, VX_PERSON.PER_NATP, VX_PERSON.PER_VERSICHERTENNUMMER, VX_PERSON.PER_LAND,
VX_ADRESSE.ADR_STRASSE, VX_ADRESSE.ADR_PLZ, VX_ADRESSE.ADR_ORT,
VX_KLASSE.KL_CODE, VX_KLASSE.KL_BEZEICHNUNG,
VX_KLASSEABSCHNITTSCHUELER.KAS_ANMELDE_STATUS,
VX_KLASSEABSCHNITTSCHUELER.KAS_ANMELDETYP, VX_KLASSEABSCHNITTSCHUELER.KAS_ABSCHNITTSNR,
VX_KLASSE_ZEITRAUM.KLZ_IS_ABSCHLUSSKLASSE, VX_KLASSE_ZEITRAUM.KLZ_ZR_NR,
VX_ZEITRAUM.ZR_BEGINN, VX_ZEITRAUM.ZR_ENDE
,'' AS FA_CODE
,'' AS FA_BEZ_STP, '' AS FA_BEZ_STP_LANG
, '' AS EcoOrig_FA_CODE, '' AS EcoOrig_FA_BEZ_STP, '' AS EcoOrig_FA_BEZ_STP_LANG
, VX_ANGEBOT.ANG_BEGINN
,*
FROM
ECOLST.VX_KLASSE_ZEITRAUM,
ECOLST.VX_PERSON,
ECOLST.VX_KLASSE,
ECOLST.VX_KLASSEABSCHNITTSCHUELER,
ECOLST.VX_ZEITRAUM,
ECOLST.VX_ADRESSE
, ECOSYS.T_KLASSE
, ECOLST.VX_ANGEBOT
WHERE
VX_KLASSE_ZEITRAUM.klz_kl_id = VX_KLASSE.kl_id
AND VX_KLASSE_ZEITRAUM.klz_zr_id = VX_ZEITRAUM.zr_id
AND VX_KLASSEABSCHNITTSCHUELER.kas_ang_id = VX_KLASSE.kl_ang_id
AND VX_KLASSEABSCHNITTSCHUELER.kas_zr_id = VX_ZEITRAUM.zr_id
AND VX_KLASSEABSCHNITTSCHUELER.kas_per_id = VX_PERSON.per_id
AND VX_KLASSEABSCHNITTSCHUELER.kas_kl_id = VX_KLASSE.kl_id
AND VX_KLASSEABSCHNITTSCHUELER.KAS_ANMELDE_STATUS LIKE 'De%' -- LIKE 'Definitiv%'
AND VX_PERSON.per_id = VX_ADRESSE.adr_per_id
AND VX_PERSON.per_man_id = VX_KLASSE.kl_man_id
AND VX_KLASSE.KL_ANG_ID = VX_ANGEBOT.ANG_ID
AND VX_KLASSE.KL_MAN_ID = 15
AND VX_KLASSE.KL_ID = T_KLASSE.KL_ID
AND T_KLASSE.KL_STATUS_ID = 491 -- d.h. TS_CODE.CODE_UP_BEZEICHNUNG = 'AKTIV'
AND VX_KLASSE.KL_KLASSENTYP_ID IN (742,743,1235,1926,2075,2076,2078,2079,2080,2081,2086,2103,2118,2119,2122,2152,2252,2308,2416)
AND VX_PERSON.PER_NP = 1 -- Natürliche Person
AND LEN(LTRIM(RTRIM(VX_PERSON.PER_VORNAME))) > 0 -- TRIM() kann erst ab SQL Server 2017 verwendet werden
AND LEN(LTRIM(RTRIM(VX_PERSON.PER_NAME))) > 0 -- TRIM() kann erst ab SQL Server 2017 verwendet werden
AND VX_ZEITRAUM.zr_beginn <= CONVERT(DATETIME, '20.05.2023', 104)
AND VX_ZEITRAUM.zr_ende >= CONVERT(DATETIME, '14.02.2023', 104)
AND VX_PERSON.per_man_id IN ( 15 )
--AND VX_Person.PER_ID IN (233777,233779)
*
一般建议在定义列列表时不要使用,但在我的例子中,, *
在末尾添加到列列表,可以显着加快查询速度。(从 21 秒减少到 2 秒)
实际执行计划中没有缺失索引建议。
, *
我认为这与在语句中使用时返回的特定列有关,这些列可能包含在查询优化器认为有用的索引中,但我不确定如何查明这些列。
为了说服 SQL Server 运行不包含, *
列列表的执行不佳的语句,使用与包含列列表中的附加项的性能语句类似的计划,我必须创建哪些索引?, *
我是否必须分析良好执行计划中使用的所有索引并创建缩减索引(省略某些列)以便查询优化器考虑对语句使用类似的良好, *
执行计划而不需要额外的?
OPTION (MIN_GRANT_PERCENT = 10, MAX_GRANT_PERCENT = 15)
应用上述解决方案仅提供了大约 1 小时的临时性能提升。之后查询恢复到错误的执行计划。我不知道为什么...
数据库兼容级别
,*
使用以下命令将数据库的兼容级别向下更改为 110 (SQL Server 2012),导致上述查询的性能不断提高,而无需在列列表中添加。
USE [master]
GO
ALTER DATABASE [ECOWEBBSP] SET COMPATIBILITY_LEVEL = 110
GO
兼容级别为 110 的查询执行计划表明,查询优化器在检索数据时选择了一种完全不同的方法,并且在分配正确的内存量 (110 MB) 方面没有任何问题。
将兼容级别设置为 110 是我唯一的选择吗?
Erik 的回答中提到的Ominous Function是由视图中的fi_kla_is_abschlussklasse
列触发的。基础表在检索数据时调用标量值函数。该函数本身返回 0 或 1,具体取决于学生是在毕业班 (1) 还是不在毕业班 (0)。VX_KLASSE_ZEITRAUM.KLZ_IS_ABSCHLUSSKLASSE
ECOLST.VX_KLASSE_ZEITRAUM
但是,在兼容级别设置为 110 (SQL Server 2012) 的情况下运行时,该函数似乎对查询持续时间的影响不大。有关详细信息,请参阅兼容级别为 110 的查询执行计划。
tl; dr - 如标题所述 - 如果我禁用表上的非聚集索引,是否仍使用该索引的链接统计信息?
我知道有很多关于“删除与禁用索引”的问题......但我找不到专门涵盖统计信息的问题。
我知道统计数据没有改变或改变(至少这是我从 MS 文档中收集到的)。但我的问题是统计数据是否仍在使用。
作为背景,我正在从事一个大型索引调优项目。它涉及在数百个具有不同工作负载模式的相同数据库中添加/删除索引。总共有超过 200 万个索引。
我的第一步是删除所有“未使用”的索引。但是,我没有放弃它们,而是考虑禁用它们以保留定义。这将允许我在表中记录任何禁用的索引的实例、数据库、对象 ID 和索引名称/ID。如果之后性能开始下降,可以重新启用(重建)索引。
但是,如果已禁用索引的统计信息仍用于生成计划...那么禁用它们不会产生与删除它们相同的性能影响。如果是这种情况,那么禁用索引就不是“真正的”性能影响测试,如果禁用的索引最终被删除,我就有引入性能问题的风险。
我的 SQL Server 版本是 SQL Server 2019 (RTM-CU18)。以下重现代码需要创建一个内存文件组。对于后续的任何人,请记住内存中的文件组一旦创建就不能从数据库中删除。
我有一个简单的内存表,我在其中插入 1 - 1200 之间的整数:
DROP TABLE IF EXISTS [dbo].[InMem];
CREATE TABLE [dbo].[InMem] (
i [int] NOT NULL,
CONSTRAINT [PK_InMem] PRIMARY KEY NONCLUSTERED (i ASC)
) WITH ( MEMORY_OPTIMIZED = ON , DURABILITY = SCHEMA_ONLY );
INSERT INTO [dbo].[InMem]
SELECT TOP (1200) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;
我还有以下本机编译的存储过程:
GO
CREATE OR ALTER PROCEDURE p1
WITH NATIVE_COMPILATION, SCHEMABINDING
AS
BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'us_english')
SELECT c1.i, c2.i, c3.i
FROM dbo.[InMem] c1
CROSS JOIN dbo.[InMem] c2
CROSS JOIN dbo.[InMem] c3
WHERE c1.i + c2.i + c3.i = 3600;
END;
GO
该过程在执行时返回一行。在我的机器上大约需要 32 秒才能完成。在执行时,我无法观察到内存使用方面的任何异常行为。
我可以创建一个类似的表类型:
CREATE TYPE [dbo].[InMemType] AS TABLE(
i [int] NOT NULL,
INDEX [ix_WordBitMap] NONCLUSTERED (i ASC)
) WITH ( MEMORY_OPTIMIZED = ON );
以及相同的存储过程,但改用表类型:
GO
CREATE OR ALTER PROCEDURE p2 (@t dbo.[InMemType] READONLY)
WITH NATIVE_COMPILATION, SCHEMABINDING
AS
BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'us_english')
SELECT c1.i, c2.i, c3.i
FROM @t c1
CROSS JOIN @t c2
CROSS JOIN @t c3
WHERE c1.i + c2.i + c3.i = 3600;
END;
GO
新存储过程在大约一分钟后抛出错误:
消息 701,级别 17,状态 154,过程 p2,第 6 行 [批处理起始行 57] 资源池“默认”中的系统内存不足,无法运行此查询。
sys.dm_os_memory_clerks
当程序执行时,我可以通过查询dmv看到 MEMORYCLERK_XTP 内存管理员使用的内存量增加到数据库的大约 2800 MB 。根据sys.dm_db_xtp_memory_consumers
DMV,几乎所有的内存使用似乎都来自“64K 页面池”消费者:
作为参考,这里是我如何执行新存储过程的。它使用与表相同的 1200 行:
DECLARE @t dbo.[InMemType];
INSERT INTO @t (i)
SELECT i
from [dbo].[InMem];
EXEC p2 @t;
生成的查询计划是一个没有阻塞运算符的简单嵌套循环计划。根据请求,这是第二个存储过程的估计查询计划。
我不明白为什么当我使用表值参数时,这样的查询的内存使用量会增长到超过 2 GB。我已经阅读了各种文档和内存中 OLTP 白皮书,但找不到任何关于此行为的参考。
使用 ETW 跟踪,我可以看到第一个过程将其大部分 cpu 时间用于调用hkengine!HkCursorHeapGetNext
,而第二个过程将其大部分 cpu 时间用于调用hkengine!HkCursorRangeGetNext
. 我还可以获得这两个程序的 C 源代码。第一个程序在这里,第二个程序有内存问题,在这里。但是,我不知道如何阅读 C 代码,所以我不知道如何进一步调查。
为什么在对表值参数执行嵌套循环时,一个简单的本机编译存储过程会使用超过 2 GB 的内存?当我在存储过程之外运行查询时,也会出现此问题。
我在 AWS i3.16xlarge 上运行 SQL Server 2022 RC1 设置,具有 2 个套接字、2 个 NUMA 节点、每个节点 32 个逻辑处理器、总共 64 个逻辑处理器。
安装程序推荐 MAXDOP 8:
但是,如果您单击该链接来配置 MAXDOP,建议会说:
根据那篇知识库文章,MAXDOP 应该是 16,而不是 8。当然,从技术上讲,8 小于 16 - 但 2、4 或 15 也是如此。8 来自哪里?
SQL Server 安装完成并启动服务后,日志显示 SQL Server 正在自动实现具有 4 个节点的 Soft-NUMA,每个节点具有 16 个逻辑处理器:
同样,这表明 MAXDOP 应该是 16。
这是一个错误,还是我错过了一些明显的东西?设置将在 MAXDOP 8 停止的地方是否还有其他不成文的规则?
SQL Server 2016 添加了AT TIME ZONE
运算符。从文档中:
AT TIME ZONE 实现依赖于 Windows 机制来跨时区转换日期时间值。
AT TIME ZONE
mscorlib.ni!TimeZoneInfo.ConvertTime
根据针对简单查询的 ETW 跟踪调用该方法。Jonathan Kehayias 有一篇博文,他从System.TimeZoneInfo
课堂上提取了所有时区规则。我只能在输出中找到 2004 年 1 月 1 日或以后生效的规则:
Rob Farley 在一篇博文中提到,2000 年的时区规则更改似乎没有得到遵守AT TIME ZONE
:
它通过使用包含所有信息的 Windows 注册表工作,但遗憾的是,当回顾过去时,它并不完美。澳大利亚在 2008 年更改了日期,美国在 2005 年更改了日期——这两个国家在一年中的大部分时间都在节约日光。AT TIME ZONE 明白这一点。但似乎并没有意识到,在 2000 年的澳大利亚,由于悉尼奥运会,澳大利亚在大约两个月前开始实行夏令时。
我觉得有大量间接证据表明,AT TIME ZONE
操作员可能会为早于 2004 年的日期返回不准确的结果。但是,我找不到任何AT TIME ZONE
使用System.TimeZoneInfo
该类的文档,AT TIME ZONE
对于较早的日期可能不准确,或者System.TimeZoneInfo
该类对于较早的日期可能不准确。
AT TIME ZONE
是否存在导致 2004 年之前返回不准确结果的 SQL Server 产品限制?
我有一个表,其中包含一个记录 ID、一个组 ID(将 1 个或多个记录链接到一个组中)和每个记录的哈希值。
CREATE TABLE HashTable(
RecordID VARCHAR(255),
GroupIdentifier VARCHAR(255),
Hash VARCHAR (255),
GroupHashList VARCHAR(4000)
)
(我知道这不是一个高效的表,但对于本示例而言,它只是一个临时表)。
我想为每个组生成一个哈希,所以我认为最简单的方法是连接组中每个记录的哈希。RecordID 是唯一的,但这些记录相关的内容不一定是唯一的,因此哈希可能是重复的。这样做的目的是标记完全重复的组,即一个组是该组中的所有记录都是另一个组中所有记录的重复。如果要将组的所有成员识别为重复组,则 GUI 需要组的所有成员具有相同的哈希值。
我正在使用 STRING_AGG 连接组中记录的各个散列,并按散列对它们进行排序,以确保我得到重复组的相同字符串。我实际上并不关心哈希的顺序是什么,只要每次都相同。当我将它作为 SELECT 查询运行时,它工作正常,我可以看到重复组的相同字符串。当我采用相同的 SELECT 查询并将其放入 UPDATE 查询时,排序似乎丢失了。
SELECT STRING_AGG([Hash],';') WITHIN GROUP (ORDER BY [Hash] ASC)
FROM HashTable
GROUP BY [GroupIdentifier]
这给出了结果(例如一对重复组):
73F294873462B2BA0E930FD16DCCB7;90E749375DF806CB6E3F5CA48FFA38;E44256CE7CFCB971EB679BAC25A697
73F294873462B2BA0E930FD16DCCB7;90E749375DF806CB6E3F5CA48FFA38;E44256CE7CFCB971EB679BAC25A697
当我将相同的代码放入 UPDATE 查询时,它没有正确排序它们:
UPDATE HashTable
SET GroupHashList = c.HashList
FROM HashTable
INNER JOIN (
SELECT (STRING_AGG([Hash],';') WITHIN GROUP (ORDER BY [Hash] ASC)) AS [HashList],
[GroupIdentifier]
FROM HashTable
GROUP BY [GroupIdentifier]) c
ON c.[GroupIdentifier] = HashTable.[GroupIdentifier]
写入表的相同两组的结果:
73F294873462B2BA0E930FD16DCCB7;90E749375DF806CB6E3F5CA48FFA38;E44256CE7CFCB971EB679BAC25A697
73F294873462B2BA0E930FD16DCCB7;E44256CE7CFCB971EB679BAC25A697;90E749375DF806CB6E3F5CA48FFA38
我错过了什么?
我第一次得到的是
Hash1; Hash2; Hash3
Hash1; Hash2; Hash3
但是当它在 UPDATE 查询中时,我得到
Hash1; Hash2; Hash3
Hash1; Hash3; Hash2
更新查询是按记录 ID 排序的,虽然不知道是不是巧合。(https://dbfiddle.uk/CPG1-z2l)
我刚刚了解到可以在表上禁用索引。甚至可以禁用聚集索引。之后,如果聚集索引,用户将无法访问索引或数据。我们什么时候禁用索引?我只是不明白用例。
我再次发现 SQL Server 和 MERGE 语句存在问题,需要一些确认。
我可以在 Azure 数据库上不断重现我的问题(但不能在本地 SQL Server 2017/2019 上)。
请执行以下步骤(一步一步,而不是一个命令执行)!
1) 架构脚本:
CREATE TABLE [dbo].[ImpactValueHistory]
(
[Rn] BIGINT NOT NULL,
[ImpactId] UNIQUEIDENTIFIER NOT NULL,
[ImpactValueTypeId] INT NOT NULL,
[Date] DATE NOT NULL,
[Value] DECIMAL(38, 10) NOT NULL,
[ValidFrom] DATETIME2 NOT NULL CONSTRAINT [DF_ImpactValueHistory_ValidFrom] DEFAULT CONVERT(DATETIME2, '0001-01-01'),
[ValidTo] DATETIME2 NOT NULL CONSTRAINT [DF_ImpactValueHistory_ValidTo] DEFAULT CONVERT(DATETIME2, '9999-12-31 23:59:59.9999999'),
[ImpactPeriodId] INT NOT NULL,
[NormalizedValue] DECIMAL(38, 10) NOT NULL,
)
GO
CREATE CLUSTERED COLUMNSTORE INDEX [COLIX_ImpactValueHistory]
ON [dbo].[ImpactValueHistory];
GO
CREATE NONCLUSTERED INDEX [IX_ImpactValueHistory_ValidFrom_ValidTo_ImpactId_DimensionItemId]
ON [dbo].[ImpactValueHistory] ([ValidFrom], [ValidTo], [ImpactId], [ImpactValueTypeId], [Date]);
GO
CREATE TABLE [dbo].[ImpactValue]
(
[Rn] BIGINT NOT NULL IDENTITY(1,1),
[ImpactId] UNIQUEIDENTIFIER NOT NULL,
[ImpactValueTypeId] INT NOT NULL,
[Date] DATE NOT NULL,
[Value] DECIMAL(38, 10) NOT NULL,
[ValidFrom] DATETIME2 GENERATED ALWAYS AS ROW START NOT NULL CONSTRAINT [DF_ImpactValue_ValidFrom] DEFAULT CONVERT(DATETIME2, '0001-01-01'),
[ValidTo] DATETIME2 GENERATED ALWAYS AS ROW END NOT NULL CONSTRAINT [DF_ImpactValue_ValidTo] DEFAULT CONVERT(DATETIME2, '9999-12-31 23:59:59.9999999'),
[ImpactPeriodId] INT NOT NULL,
[NormalizedValue] DECIMAL(38, 10) NOT NULL,
PERIOD FOR SYSTEM_TIME ([ValidFrom], [ValidTo]),
CONSTRAINT [PK_ImpactValue] PRIMARY KEY NONCLUSTERED ([ImpactId], [ImpactValueTypeId], [Date], [ImpactPeriodId])
)
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[ImpactValueHistory]))
GO
CREATE UNIQUE CLUSTERED INDEX [IX_ImpactValue_Id] ON [dbo].[ImpactValue]([Rn])
GO
CREATE COLUMNSTORE INDEX [CIX_ImpactValue] ON [dbo].[ImpactValue] ([ImpactId], [ImpactValueTypeId], [Date], [Value], [NormalizedValue])
GO
2) 插入一些随机数据的脚本
DECLARE @inserted0 TABLE ([Date] DATE, [ImpactId] uniqueidentifier, [ImpactPeriodId] int, [ImpactValueTypeId] int);
MERGE [dbo].[ImpactValue] USING (
SELECT TOP 278 -- <-- this number is critical
DATEADD(MONTH, ROW_NUMBER() OVER(ORDER BY [Name]) - 1, '2000-01-01') AS [Date],
NEWID() AS [ImpactId],
1 AS [ImpactPeriodId],
1 AS [ImpactValueTypeId],
99 AS [Value],
99 AS [NormalizedValue]
FROM [sys].[all_columns]
) AS i ([Date], [ImpactId], [ImpactPeriodId], [ImpactValueTypeId], [Value], [NormalizedValue]) ON 1=0
WHEN NOT MATCHED THEN
INSERT ([Date], [ImpactId], [ImpactPeriodId], [ImpactValueTypeId], [Value], [NormalizedValue])
VALUES (i.[Date], i.[ImpactId], i.[ImpactPeriodId], i.[ImpactValueTypeId], i.[Value], i.[NormalizedValue])
OUTPUT INSERTED.[Date], INSERTED.[ImpactId], INSERTED.[ImpactPeriodId], INSERTED.[ImpactValueTypeId]
INTO @inserted0;
SELECT * FROM @inserted0
这一步应该返回所有插入的行!
3)从步骤2中删除数据) 这一步正在填充配置的历史表
DELETE [dbo].[ImpactValue]
4)再次插入一些随机数据 您可以使用步骤2中的脚本)
我必须注意,步骤 1) - 4) 应该单独执行,而不是在GO
.
这一步应该再次返回所有插入的行!但事实并非如此! 在我这边,我总是得到一个空的结果。这可以在我们的三个生产数据库上重现:(
MERGE 语句由 EF Core 生成,目前我正在通过设置 Max Batch Size 来解决此问题。但这不可能是最终的解决方案。
它必须与在时态表上配置了非聚集索引的时态表有关。
也可以看看:
在过去,我已经偶然发现了这个问题:
但是我当前的问题只能在 Azure SQL 数据库上重现,并且不会引发任何错误。
有趣的旁注:
如果没有历史表上的 COLUMNSTORE 索引,它就可以工作。通过仅删除主表上的 COLUMNSTORE 索引,我看到了同样的问题。
TOP 278
(a) 重现问题和 (b) 不重现的情况的实际执行计划TOP (@BatchSize)
可在https://1drv.ms/u/s!AsOa6e9ukBWQlIRg9_9eySDFp5hvEA?e=KBQBsP获得。我还添加了批量大小为 277 的实际执行计划。两者都使用这个大小!
针对勒索软件的最佳保护之一是将所有数据库文件备份到一个完全独立的系统。我们已经做到了。
但一种想法是数据库的备份现在可能包含勒索软件。这可能吗?这是 2016 SQL Server 本机创建的 .bak。还是勒索软件不可能将自己嵌入到备份文件中?
我在 MS SQL Server 中有一张表。
表用法:来自 Web 服务调用的日志记录 99.9% 是来自日志记录的用法,开发人员很少在 Prod 中查看此表(仅在报告或研究问题时)。
主键:基于“INT”数据类型的“ID”。有一个基于该“ID”列的聚集索引。
我对此更改的意图:想要管理此表(因为它有 10 年的数据)并继续前进(由于新要求),开发人员/分析师有可能进一步深入研究此表(仅几个月)而且我不想为相同的目的创建一个新表。
我的问题:
[主要问题]我可以根据“DateCreated”(DATETIME,NOT NULL 列)对该表进行分区,而不会导致问题(逻辑/性能方面)。
[很高兴知道]需要多少时间(我知道这取决于数据库空间/服务器内存和其他详细信息,但大致 # 会很好)对这个巨大的表进行分区(如果可以根据日期进行分区) . 问这个问题,因为这是一个生产表,并且经常插入行(现在 ~ 350 条记录/分钟)。
【不完全是个问题,求推荐】有没有更好的方案来管理这张表(不想在Production中保留超过3年的数据,方案在下面提到)?
当前计划(我是 MS SQL 的新手,所以这是我想出的):
- 每个分区保留 3 个月的数据。
- 系统在每个季度之前自动创建分区。
- 在活动表中只保留 3 年的分区。
- 将其他分区移动到 OLD/ARCHIEVE 表(需要创建这个)。真正要清除的旧数据。
在 PostgreSQL 和 SQL Server 中,默认页面大小为 8 KB,在 MySQL 中为 16 KB,在 IBM DB2 和 Oracle 中仅为 4 KB。
为什么这些页面尺寸这么小?
是否有历史或内存使用原因?
我们启用了 sp_configure 'tempdb metadata memory-optimized' = 1,现在 tempdb 元数据在我们的一台服务器上占用了 400 GB 以上,并且还在继续增长。内存使用量有所下降,但通常它会不断增长它的内存使用量。我们已经有几次服务器实际上崩溃了,因为没有足够的内存供其他系统进程修改 tempdb 并导致整个服务器停机。
如何防止 SQL Server 内存中优化的 tempdb 元数据持续增长并使我的服务器崩溃?如果有的话,我可以查看哪些其他信息来找出消耗这么多内存的原因?
以下查询当前返回 438 GB。
SELECT SUM(domc.pages_kb / 1024.0 / 1024.0) AS pages_gb
FROM sys.dm_os_memory_clerks AS domc
WHERE domc.type LIKE 'MEMORYCLERK_XTP'
以下查询提供了内存使用量最大 (290 GB) 为 memory_consumer_id of 113 - 'LOB Page Allocator' 的数据。它没有object_id 或xtp_object_id,所以我猜它是一个数据库范围的对象。
SELECT ddxmc.memory_consumer_id
, ddxmc.memory_consumer_type_desc
, ddxmc.memory_consumer_desc
, ddxmc.object_id
, ddxmc.xtp_object_id
, ddxmc.used_bytes / 1024.0 / 1024.0 / 1024.0 AS used_gb
FROM sys.dm_db_xtp_memory_consumers AS ddxmc
ORDER BY ddxmc.allocated_bytes DESC
版本:SQL Server 2019 CU9 -
机箱上的企业内存:3 TB
实例类型:故障转移集群实例
操作系统:Windows Server 2019 标准
CPU 核心数:80 个物理核心(我们最近不得不增加这个数字) Tempdb 的数量文件:64 复制:此服务器是事务复制的发布者和订阅者。
我们是 tempdb 的重度用户。我们一直在存储过程中不断地创建和删除临时表和表变量。我们需要处理大量数据,传入数据列表,然后将这些数据列表转换为表格来处理结果集数据,而不是一次处理一条信息。由于 tempdb 的大量流失,我们不得不实现内存中优化的 tempdb 以减轻我们遇到的 PAGELATCH_* 等待。
我很确定没有任何内存优化的用户表。当我运行以下查询时,我只看到SYSTEM_TABLE
类型对象:
SELECT *
FROM sys.dm_db_xtp_index_stats AS ddxis
JOIN sys.objects AS o ON o.object_id = ddxis.object_id
我很清楚垃圾收集只能发生在比最旧事务更早的行上,因此我已经停止了所有长时间运行的 SQL 代理作业和任何其他超过 5 分钟的进程。不幸的是,我们的内存使用量并没有回落。我们确实有旧会话,但根据以下查询,它们都没有打开任何事务。
SELECT *
FROM sys.dm_exec_sessions AS des
WHERE des.open_transaction_count > 0
为了排除复制,我确实暂时停止了此服务器的发布和订阅代理。内存使用没有变化。
我在 tempdb 中运行了“CHECKPOINT”。内存使用量没有下降。
为了删除正在缓存的旧临时表,我清除了查询计划缓存,并通过运行以下查询验证了临时表是否已重新创建。这并没有导致内存使用量大幅下降。
SELECT *
FROM sys.tables AS t
WHERE t.name LIKE '#%'
AND t.create_date < 'TimeOfClearingPlanCache'
我们可以重新启动 SQL Server,它确实可以缓解一段时间的问题,但一段时间后内存使用量确实会再次增加。虽然这是一种解决方法,但这是一个我们不太喜欢的糟糕解决方案。
我们可以关闭“内存中优化的 tempdb”,但是我们会受到我们之前经历过的大量 PAGELATCH_* 等待的影响。我们有 64 个 tempdb 文件来帮助减少我们看到的争用,但在我们繁忙的时期,即使这样也不够。这可能是一种选择,但最好是我们能找到内存使用量持续增长的原因。
SQL Server的GO
语句引起了我极大的好奇心,我真的不知道如何正确使用它。
我注意到无论有没有查询GO
都不会返回错误并且似乎工作相同,那么它的目的是什么,我为什么要使用它?
;
分号和GO
查询末尾有什么区别?
我现在负责 SQL 开发的同事说我永远不应该使用OR
语句,因为它会弄乱查询优化器并忽略产生缓慢查询的表索引。我在谷歌搜索时找不到任何这样的例子。以下查询的替代方案变得非常难看,有十几个代码块,这些代码块看起来几乎相同(与示例相同),对每个变量状态使用 if else 语句。注意检查短路的变量,如果值为 2,则返回所有结果,否则按字段过滤。
我询问了一些包含这些关于为什么不使用OR
语句的声明的资源,并收到了以下链接(我们使用的是 MS SQL Server)。
这些示例似乎都不像下面的当前实现。我很难相信这段代码有问题,但如果有问题请告诉我。我还想了解更多关于不使用的评论OR
实际上可能成立的信息以及为什么如此,以便更好地理解这个问题。
SELECT
e.EmployeeName,
e.DepartmentName,
crs.Title,
c.Name as CompanyName
FROM Employee E
Left Outer Join Company c ON c.Id = @companyId
INNER JOIN Department d on e.DepartmentId = d.Id
WHERE
c.Id = @companyId
AND (@Active = 2 OR crs.IsActive = @Active)
AND (@Dot = 2 OR IsDot = @Dot)
AND crs.CompanyId = @companyId
AND d.CompanyId = @companyId
ORDER BY EmployeeName, Title, PassedDate
我相信除非有充分的理由,否则复制代码总是不好的。测试查询后,我确认使用了正确的索引。提到这一点后,我被告知他会使用最佳实践。我还没有看到任何最佳实践告诉我不要使用OR
. 谁能带我去这些?
这是我很久以前更新之前的可憎之处。如果您发现@Department
并对此感到疑惑,那不是错误。Telerik 报告组件正在对这段代码做一些事情,并在后台扩展一个数组,然后再到达服务器。
IF @Active = 2
BEGIN
--ACTIVE AND INACTIVE
IF 0 IN (@Department)
BEGIN
IF @DOT = 1
BEGIN
Select
A.LastName + ', ' + A.FirstName as EmployeeName,
A.DepartmentName,
C.Title,
ISNULL(B.Comments, ' ') as Remarks,
CONVERT(varchar, B.PassedDate, 101) as DateOut,
D.Name as CompanyName
FROM Employee A
Left Outer Join EmployeeCourse B ON A.Id = B.EmployeeId
Left Outer Join CompanyCourse C ON B.CompanyCourseId = C.Id
Left Outer Join Company D ON @companyId = D.Id
WHERE A.CompanyId = @companyId
AND B.PassedDate IS Not NULL
AND C.DotCourse = 1
ORDER BY EmployeeName, Title, PassedDate
END
IF @DOT = 0
BEGIN
Select
A.LastName + ', ' + A.FirstName as EmployeeName,
A.DepartmentName,
C.Title,
ISNULL(B.Comments, ' ') as Remarks,
CONVERT(varchar, B.PassedDate, 101) as DateOut,
D.Name as CompanyName
FROM Employee A
Left Outer Join EmployeeCourse B ON A.Id = B.EmployeeId
Left Outer Join CompanyCourse C ON B.CompanyCourseId = C.Id
Left Outer Join Company D ON @companyId = D.Id
WHERE A.CompanyId = @companyId
AND B.PassedDate IS Not NULL
ORDER BY EmployeeName, Title, PassedDate
END
IF @DOT = 2
BEGIN
Select
A.LastName + ', ' + A.FirstName as EmployeeName,
A.DepartmentName,
C.Title,
ISNULL(B.Comments, ' ') as Remarks,
CONVERT(varchar, B.PassedDate, 101) as DateOut,
D.Name as CompanyName
FROM Employee A
Left Outer Join EmployeeCourse B ON A.Id = B.EmployeeId
Left Outer Join CompanyCourse C ON B.CompanyCourseId = C.Id
Left Outer Join Company D ON @companyId = D.Id
WHERE A.CompanyId = @companyId
AND B.PassedDate IS Not NULL
AND C.DotCourse = 0
ORDER BY EmployeeName, Title, PassedDate
END
END
ELSE
BEGIN
IF @DOT = 1
BEGIN
Select
A.LastName + ', ' + A.FirstName as EmployeeName,
A.DepartmentName,
C.Title,
ISNULL(B.Comments, ' ') as Remarks,
CONVERT(varchar, B.PassedDate, 101) as DateOut,
D.Name as CompanyName
FROM Employee A
Left Outer Join EmployeeCourse B ON A.Id = B.EmployeeId
Left Outer Join CompanyCourse C ON B.CompanyCourseId = C.Id
Left Outer Join Company D ON @companyId = D.Id
WHERE A.CompanyId = @companyId
AND B.PassedDate IS Not NULL
AND C.DotCourse = 1
AND A.DepartmentId IN (@Department)
ORDER BY EmployeeName, Title, PassedDate
END
IF @DOT = 0
BEGIN
Select
A.LastName + ', ' + A.FirstName as EmployeeName,
A.DepartmentName,
C.Title,
ISNULL(B.Comments, ' ') as Remarks,
CONVERT(varchar, B.PassedDate, 101) as DateOut,
D.Name as CompanyName
FROM Employee A
Left Outer Join EmployeeCourse B ON A.Id = B.EmployeeId
Left Outer Join CompanyCourse C ON B.CompanyCourseId = C.Id
Left Outer Join Company D ON @companyId = D.Id
WHERE A.CompanyId = @companyId
AND B.PassedDate IS Not NULL
AND A.DepartmentId IN (@Department)
ORDER BY EmployeeName, Title, PassedDate
END
IF @DOT = 2
BEGIN
Select
A.LastName + ', ' + A.FirstName as EmployeeName,
A.DepartmentName,
C.Title,
ISNULL(B.Comments, ' ') as Remarks,
CONVERT(varchar, B.PassedDate, 101) as DateOut,
D.Name as CompanyName
FROM Employee A
Left Outer Join EmployeeCourse B ON A.Id = B.EmployeeId
Left Outer Join CompanyCourse C ON B.CompanyCourseId = C.Id
Left Outer Join Company D ON @companyId = D.Id
WHERE A.CompanyId = @companyId
AND B.PassedDate IS Not NULL
AND C.DotCourse = 0
AND A.DepartmentId IN (@Department)
ORDER BY EmployeeName, Title, PassedDate
END
END
END
ELSE
BEGIN
--ACTIVE OR INACTIVE
IF 0 IN (@Department)
BEGIN
IF @DOT = 1
BEGIN
Select
A.LastName + ', ' + A.FirstName as EmployeeName,
A.DepartmentName,
C.Title,
ISNULL(B.Comments, ' ') as Remarks,
CONVERT(varchar, B.PassedDate, 101) as DateOut,
D.Name as CompanyName
FROM Employee A
Left Outer Join EmployeeCourse B ON A.Id = B.EmployeeId
Left Outer Join CompanyCourse C ON B.CompanyCourseId = C.Id
Left Outer Join Company D ON @companyId = D.Id
WHERE A.IsActive = @Active
AND A.CompanyId = @companyId
AND B.PassedDate IS Not NULL
AND C.DotCourse = 1
ORDER BY EmployeeName, Title, PassedDate
END
IF @DOT = 0
BEGIN
Select
A.LastName + ', ' + A.FirstName as EmployeeName,
A.DepartmentName,
C.Title,
ISNULL(B.Comments, ' ') as Remarks,
CONVERT(varchar, B.PassedDate, 101) as DateOut,
D.Name as CompanyName
FROM Employee A
Left Outer Join EmployeeCourse B ON A.Id = B.EmployeeId
Left Outer Join CompanyCourse C ON B.CompanyCourseId = C.Id
Left Outer Join Company D ON @companyId = D.Id
WHERE A.IsActive = @Active
AND A.CompanyId = @companyId
AND B.PassedDate IS Not NULL
ORDER BY EmployeeName, Title, PassedDate
END
IF @DOT = 2
BEGIN
Select
A.LastName + ', ' + A.FirstName as EmployeeName,
A.DepartmentName,
C.Title,
ISNULL(B.Comments, ' ') as Remarks,
CONVERT(varchar, B.PassedDate, 101) as DateOut,
D.Name as CompanyName
FROM Employee A
Left Outer Join EmployeeCourse B ON A.Id = B.EmployeeId
Left Outer Join CompanyCourse C ON B.CompanyCourseId = C.Id
Left Outer Join Company D ON @companyId = D.Id
WHERE A.IsActive = @Active
AND A.CompanyId = @companyId
AND B.PassedDate IS Not NULL
AND C.DotCourse = 0
ORDER BY EmployeeName, Title, PassedDate
END
END
ELSE
BEGIN
IF @DOT = 1
BEGIN
Select
A.LastName + ', ' + A.FirstName as EmployeeName,
A.DepartmentName,
C.Title,
ISNULL(B.Comments, ' ') as Remarks,
CONVERT(varchar, B.PassedDate, 101) as DateOut,
D.Name as CompanyName
FROM Employee A
Left Outer Join EmployeeCourse B ON A.Id = B.EmployeeId
Left Outer Join CompanyCourse C ON B.CompanyCourseId = C.Id
Left Outer Join Company D ON @companyId = D.Id
WHERE A.IsActive = @Active
AND A.CompanyId = @companyId
AND B.PassedDate IS Not NULL
AND C.DotCourse = 1
AND A.DepartmentId IN (@Department)
ORDER BY EmployeeName, Title, PassedDate
END
IF @DOT = 0
BEGIN
Select
A.LastName + ', ' + A.FirstName as EmployeeName,
A.DepartmentName,
C.Title,
ISNULL(B.Comments, ' ') as Remarks,
CONVERT(varchar, B.PassedDate, 101) as DateOut,
D.Name as CompanyName
FROM Employee A
Left Outer Join EmployeeCourse B ON A.Id = B.EmployeeId
Left Outer Join CompanyCourse C ON B.CompanyCourseId = C.Id
Left Outer Join Company D ON @companyId = D.Id
WHERE A.IsActive = @Active
AND A.CompanyId = @companyId
AND B.PassedDate IS Not NULL
AND A.DepartmentId IN (@Department)
ORDER BY EmployeeName, Title, PassedDate
END
IF @DOT = 2
BEGIN
Select
A.LastName + ', ' + A.FirstName as EmployeeName,
A.DepartmentName,
C.Title,
ISNULL(B.Comments, ' ') as Remarks,
CONVERT(varchar, B.PassedDate, 101) as DateOut,
D.Name as CompanyName
FROM Employee A
Left Outer Join EmployeeCourse B ON A.Id = B.EmployeeId
Left Outer Join CompanyCourse C ON B.CompanyCourseId = C.Id
Left Outer Join Company D ON @companyId = D.Id
WHERE A.IsActive = @Active
AND A.CompanyId = @companyId
AND B.PassedDate IS Not NULL
AND C.DotCourse = 0
AND A.DepartmentId IN (@Department)
ORDER BY EmployeeName, Title, PassedDate
END
END
END
注意:我在初始代码示例中删除了一些以简化。
根据他的解释、链接和我的搜索,我很难相信我当前的解决方案在性能和可读性方面不是最好的。我接受可能存在一些可能会降低性能的情况,但我高度怀疑我的实施是否属于这种情况。OR
当我没有看到任何说明这一点的文档时,我不喜欢永远不应该使用的想法。
当我显示查询时,我被告知作为概括永远不要使用OR
. 我现在正在做功课,看看这是否是有效的信息。我真的很讨厌使用以前的代码,因为错误的方式太多了。
我需要截断一个包含 170 亿行的表,该表位于作为 AG 一部分的数据库中。
此操作对 AG 延迟和日志备份大小有何影响?
有推荐的方法吗?
我想创建一个被拒绝写入任何数据库的登录名 - 不仅仅是今天存在的数据库,还有任何新创建的数据库或从其他服务器恢复的数据库。
我不能使用仅数据库角色(甚至在模型中),因为这些角色不会对新恢复的数据库生效。
(商业目的:我正在写一篇关于人们如何为自己设置低权限的新登录的博客文章,并且他们可以确保他们在复制/粘贴代码时不会意外地出现“糟糕”的时刻或在没有 where 子句的情况下运行它。)
[更新:此问题描述了已在SQL Server 2019 的累积更新 5 中修复的错误。]
考虑以下复制示例(小提琴):
CREATE FUNCTION dbo.Repro (@myYear int)
RETURNS datetime
AS
BEGIN
IF @myYear <> 1990
BEGIN
RETURN NULL
END
DECLARE @firstOfYear datetime;
SET @firstOfYear = DATEFROMPARTS(@myYear, 1, 1);
IF DATEDIFF(day, @firstOfYear, @firstOfYear) <> 0
BEGIN
RETURN NULL
END
RETURN @firstOfYear
END
SELECT dbo.Repro(0);
显然,如果输入为1990
,则该函数应返回 1990 年 1 月的第一天,NULL
否则返回。是的,我知道这DATEDIFF(day, @firstOfYear, @firstOfYear) <> 0
是一个荒谬的操作。这是一个演示潜在错误的mcve ,而不是生产代码。
现在让我们SELECT dbo.Repro(0)
在 SQL Server 2017 和 SQL Server 2019 上执行。
预期结果:NULL
。
SQL Server 2017 上的实际结果:NULL
SQL Server 2019 上的实际结果:
消息 289 级别 16 状态 1 行 1
无法构造数据类型日期,某些参数的值无效。
显然,SQL Server 2019 会执行初始保护子句 ( IF @myYear <> 1990
) 下面的一些代码,即使它不应该执行。
我的问题: