表中有一些列可能会包含一些非 ANSI 字符,我们必须存储它们。在 SQL Server 2019 之前,只有 1 个选项 -NVARCHAR
数据类型,但是从 SQL Server 2019 开始,可以使用VARCHAR
以 结尾的排序规则的数据类型%_UTF8
。我理解,为了完全兼容大小,我需要将VARCHAR
列大小加倍,所以如果是,VARCHAR(20)
则需要VARCHAR(40)
覆盖如果有人插入所有 20 个字符都是非 ANSI 的文本的情况,但这种排序规则还有其他缺点吗?该列中的数据将只有 ~99.9% 的 ANSI 字符。
对于示例数据...
/*Quick and dirty generation of some rows of data*/
SELECT value as [orderid],
1 as [custid],
1 as [empid],
1 as [shipperid],
getdate() as [orderdate],
'abcdefgh' as [filler]
INTO dbo.Orders
FROM generate_series(1,10000000)
CREATE CLUSTERED INDEX [idx_cl_od] ON [dbo].[Orders]
(
[orderdate] ASC
)
UPDATE STATISTICS dbo.Orders WITH FULLSCAN
以及以下查询
SELECT [orderid], [custid], [empid], [shipperid], [orderdate], [filler]
FROM dbo.Orders
WHERE orderid <=7601715 AND 1=1 /*Prevent simple parameterisation*/
然后在我的开发机器(SQL Server 2022,DOP 为 4)上,聚集索引扫描的 IO 成本46.8853
与串行或并行计划无关。扫描的 CPU 成本11.0002
在串行计划和 2.75004
并行计划中,因此我预计计划之间的临界点是并行运算符超出时8.25016
(估计进入的行数约为 450 万时达到的阈值)。实际上,在实际发生这种情况时,收集流运算符的成本是13.0501
(比我预期的高出约 300 万行)。
如果 SQL Server 不使用总体计划成本作为临界点,那么实际逻辑是什么?
我有一个多租户应用程序。因此,单个数据库包含所有使用该数据库的组织的数据。
有没有办法提供对数据库的访问权限,以便我的客户可以使用任何分析工具(例如 Power BI)连接到它。
有 Org 表 - 我可以在其中指定他们可以获取的该表的 PK。
然后是 Event 表,它有一个到 Org 表的 FK。
然后是注册(到事件)表,其中包含指向事件的 FK 和指向用户的 FK。
最困难的是用户表。如果用户注册了活动,那么该客户就可以获取该用户。这意味着多个客户可以看到该用户表行。
这是专门针对 SQL Server 的。因此,我需要制作一些视图来确定给定用户可以查看哪些用户记录。虽然我可以在 Event 表中复制 Ord PK,但用户记录(因为多个客户可以获得它)无法与添加的 FK 一起使用。
有办法吗?
我观察(并重现)了 SQL Server 2022 中的以下场景。
使用中的模式
- 代码通过 sp_executesql 执行(不涉及存储过程)
- 第一个查询将数据选择到临时表中
- 然后,DDL 语句会在临时表上创建聚集索引。临时表绝对不可缓存——首先,这不是一个模块(存储过程或函数),而且我们在临时表填充后创建索引。因此,我不希望缓存的临时对象上留下的统计信息在这里被涉及。
- 查询从临时表中选择数据。此查询每次都会获得完全优化(不是 TRIVIAL 计划)
该批处理可以针对小型和大型数据集运行,因此临时表中可以包含 1 行或数千行。
此行为通常发生在可读辅助数据库上。没有可写查询存储,也没有自动计划强制因素。
我已经验证我也可以针对主副本重现该行为。(自动计划更正被告知忽略查询,并且我确认重现时没有在主副本上强制执行计划。)
复制脚本
- 安装脚本- 我在 SQL Server 2022 CU15 上运行了此脚本。这会关闭查询存储并使用兼容级别 130。
- 重现查询- 我一直通过 SQL Query Stress 运行该查询,因此我可以轻松地在一个或多个线程上同时运行该查询
- 计划生成编号和临时表- 一个非常简单的查询,用于观察系统查询统计中的 plan_generation_num(“重新编译后可用于区分计划实例的序列号。”)和当前的临时表列表
通常会发生什么——以及我期望的行为
通常,在查询执行之间更改临时表中的大量行会自动导致重新编译,并且我会看到从临时表中选择数据的查询具有与临时表中的行匹配的行估计值。
当它按预期工作时,性能良好。
使用重现查询:如果我清除计划缓存,然后在 SQL 查询压力中的单个线程上运行重现查询 40 次迭代,plan_generation_number 最终为 82。当使用 sp_WhoIsActive 对查询计划进行采样时,查询临时表的行估计值与临时表中的行数匹配,正如预期的那样。
有时会发生的情况——在我看来像是一个错误
在极少数情况下,我看到正在使用的计划中,临时表的估计计划只有 1 行,但临时表中实际上有大量行。很多行已更改,但它没有自动重新编译:
这会导致性能非常缓慢,因为低估计计划决定使用没有预取的嵌套循环,这使其成为 CPU 消耗器。
使用重现查询:如果我清除计划缓存,然后在 SQL Query Stress 中的 2 个线程上运行重现查询 20 次迭代,则 plan_generation_number 最终会小于 82——它因运行而异,但可能是 72 或 59,表示重新编译次数较少。在运行时,我还可以使用 sp_WhoIsActive 对估计的行数为单个但临时表中的行数更多的情况进行采样。屏幕截图:
我只能在多个并发会话上运行重现代码时重现此问题
我无法在 SQL Server 中使用单个会话重现此行为。我能重现此行为的唯一方法是设置一个代码块:
- 执行至少 1 次 sp_executesql 语句的迭代,该语句在临时表中有 1 行
- 然后执行 sp_executesql 语句的 1 次迭代,该语句在临时表中包含更多行
如果我在单个会话中运行此程序,则无法重现问题。但如果我在四五个会话中同时运行此程序,则偶尔会出现“未按应有的方式重新编译”的问题。(注意:使用 SQL Query Stress,我只需 2 个会话/迭代即可重现此问题。)
我觉得这像是一个 bug,我很好奇是否有人见过它。不过,使用临时表重新编译和统计行为非常复杂,因此我可能忽略了它如何与不可缓存的临时表一起工作的一些细微差别。
PS:我确实认为可缓存的临时表通常更好。我只是想弄清楚为什么这种行为会在此时在不可缓存的临时表场景中发生。
解决方法
在查询中添加后option (recompile)
,我无法再重现查询临时表的 1 行计划的重用。这已经足够了,但我很困惑为什么这是必要的。
我在 SQL Server 2019 上有两台服务器,它们随机决定停止 SQL Server Browser 服务。尝试重新启动它时,它会立即自行关闭。使用下面的命令提示符在数据库主机服务器上以控制台模式启动以获取更多反馈,我看到它已成功启动,但到了“未找到已安装的 SQL 引擎实例 - 未在 SSRP 上监听”的地步,这时它又自行关闭了。
C:\Windows\system32>"C:\Program Files (x86)\Microsoft SQL Server\90\Shared\sqlbrowser.exe" -c
SQLBrowser: starting up in console mode
SQLBrowser: starting up SSRP redirection service
SQLBrowser is successfully listening on ::[1434]
SQLBrowser is successfully listening on 0.0.0.0[1434]
SQLBrowser: Found no installed SQL engine instances -- not listening on SSRP.
SQLBrowser: Both SSRP and OLAP redirection services are disabled. Shutting down browser service
我能找到的唯一信息是关于 SSRP 丢失或禁用的注册表项,我已经验证它已正确到位。
HKLM\SOFTWARE\Wow6432Node\Microsoft\Microsoft SQL Server\90\SQL Browser\SSRPListener
Key 的值设置为 1。为了验证,我将其设置为 0,此时 SQL Browser 根本不会启动。返回 1 后,它会恢复到原始行为,即成功启动,然后立即关闭。
同时,数据库本身是可以访问的,所以我知道确实安装了 SQL 引擎实例,它们应该是可见/可监听的,而我完全不知道该如何继续。任何一丝想法都会受到赞赏。
编辑以添加屏幕截图,左侧是出现故障的 SQL Server 2019,右侧是仍按预期工作的 SQL Server 2014。
我有 2 个 MSSQL 数据库,它们不属于同一个集群,但它们是手动同步的(没有配置数据库镜像)
我希望我的客户端应用程序尝试连接到第一个 DB 主机。如果失败,它应该连接到第二个 DB 主机。
我创建了一个如下所示的连接字符串(用 Java 编写):
String connectionString = "jdbc:sqlserver://<IP_ADDRESS_SERVER_1>:1433;"
+ "database=<DB_NAME>;"
+ "user=<USERNAME>;"
+ "password=<PASSWORD>;"
+ "encrypt=true;"
+ "trustServerCertificate=true;"
+ "failoverPartner=<IP_ADDRESS_SERVER_2>:1433;";
它正确连接到主数据库实例。但是如果我关闭它并且连接重定向到第二个数据库实例,则连接将被阻止。(将此实例连接为主要工作)
重定向连接是否依赖于镜像?
我需要使用启用排序的STRING_SPLIT :
我们已将所有服务器升级到 SQL Server 2022 并将兼容级别更改为 160,但以下代码不起作用:
SELECT * FROM STRING_SPLIT('Lorem ipsum dolor sit amet.', ' ', 1);
错误是:
消息 8748,级别 16,状态 1,第 1 行 STRING_SPLIT 的 enable_ordinal 参数仅支持常量值(不支持变量或列)。
生产环境是:
Microsoft SQL Server 2022 (RTM-CU11) (KB5032679) - 16.0.4105.2 (X64)
Nov 14 2023 18:33:19
Copyright (C) 2022 Microsoft Corporation
Standard Edition (64-bit) on Windows Server 2019 Standard 10.0 <X64> (Build 17763: )
SSMS 的版本是:
我们已经在最新的 CU 上测试了代码:
我唯一找到的就是这个答案,它指出:
问题在于,SSMS 的 IntelliSense/工具提示没有基于版本的条件逻辑,并且代码领先于引擎。目前,该功能仅在 Azure SQL 数据库、托管实例和 Synapse 中可用。
不过,我不确定问题出在哪里——文档、引擎、SSMS 还是我做错了什么。
注意:Azure Data Studio 上也存在同样的问题。
我想将版本信息添加到我们的数据库中 - 它的全部用途是检查用户读取行之后的版本是否已更改。我正在考虑为_VERSION
所涉及的二十几个表添加一个名为的列。作为批处理UPDATE
语句的一部分,我会使用类似于的内容SET ..._VERSION=something_or_other
。我不知道此列包含哪些数据。
我见过的一个解决方案是使用 datetime 列,然后从 sys.dm_tran_active_transactions 获取事务开始时间,选择 的行CURRENT_TRANSACTION_ID()
。对于我们的需求,1/300 的精度就足够了。我理解CURRENT_TIMESTAMP
在这种情况下使用不太有用,因为这会随着批处理的进行而改变。
如果这是规范的话,我会这么做。这很容易实现。
但对于真正简单的用途来说,这是最好的方法吗?是否有其他值sys.dm_tran...
可以提供相同的结果,但可能比日期时间更容易存储和定位?
注意:是的,我知道时间表和 MS 的变化跟踪,但被告知不要使用它们。
我有一堆文件:两个备份和一个事务日志列表。
备份称为:
- FB20241125_233033.bak
- FB20241126_233040.bak
(文件名与备份的日期/时间相对应。)
事务日志被称为(命令行摘录):
Prompt>dir *.trn
Directory of C:\...\Transaction_Logs
25/11/2024 23:45 5.758.464 20241125224500.trn
26/11/2024 00:00 5.560.832 20241125230001.trn
26/11/2024 00:15 5.692.928 20241125231501.trn
...
26/11/2024 15:00 5.822.976 20241126140001.trn
26/11/2024 15:15 5.955.072 20241126141501.trn
26/11/2024 15:30 5.889.536 20241126143000.trn
27/11/2024 12:15 5.626.368 20241127111501.trn
(文件名的原因相同。)
然后我使用以下属性恢复该目录(获取两个备份文件和所有事务日志文件):
这是我收到的错误消息:
System.Data.SqlClient.SqlError: 此备份集中的日志开始于 LSN 33845000000619000001,该日志太新,无法应用于数据库。可以恢复包含 LSN 33816000000750400001 的早期日志备份。(Microsoft.SqlServer.SmoExtended)
我自己想“但这很明显:我最近的备份是在 2024 年 11 月 26 日 23 时 30 分 40 秒进行的,而最新的事务日志的日期是后一天,所以我们不要在恢复中包含该事务日志。 ”,但这没有帮助。
有人知道我该怎么做才能恢复包含最新事务日志的最新备份?
编辑1:“*.trn”文件中的LSN怎么样?
同时,我对提到的*.trn
文件运行了以下SQL查询:
RESTORE HEADERONLY FROM DISK = 'C:\...\20241125224500.trn';
RESTORE HEADERONLY FROM DISK = 'C:\...\20241125230001.trn';
...
以下是第一批结果:
FirstLSN LastLSN
---------------------- --------------------
33758000001254200001 33759000000515900001
33759000000515900001 33759000001579300001
33759000001579300001 33760000000835100001
33760000000835100001 33761000000090800001
33761000000090800001 33761000001176400001
33761000001176400001 33762000000412500001
33762000000412500001 33762000001492100001
33762000001492100001 33763000000648100001
33763000000648100001 33763000001722200001
如您所见,有相当多的*.trn
文件,其第一个日志序列号(LSN)为3375...
和3376...
,那么为什么错误消息说此备份集中的日志开始于LSN 3384...
?
提前致谢
我正在向一个大表添加检查约束,因为我想准备使用分区切换将其切换为分区表。
该检查是对列的简单不等式检查,并且该列上有索引。但是当我添加约束时,SQL Server 仍然会扫描整个表。是否可以快速添加约束并保持其可信?我希望这可以通过索引来实现。
以下是该扫描的再现图:
USE tempdb;
GO
SELECT *
INTO MY_MESSAGES
FROM sys.messages;
CREATE CLUSTERED INDEX IX_MY_MESSAGES ON dbo.MY_MESSAGES(message_id);
SET STATISTICS IO ON;
ALTER TABLE dbo.MY_MESSAGES
ADD CONSTRAINT CK_mymessages CHECK (message_id < 50000);
SET STATISTICS IO OFF;
DROP TABLE IF EXISTS dbo.MY_MESSAGES;
这显示在消息选项卡中:
Table 'MY_MESSAGES'. Scan count 1, logical reads 10955