在我正在处理的一个项目中,必须跟踪对数据库某些表中行的每次更改以进行进一步审计或回滚。必须很容易找到谁修改了行,从哪个 IP 地址和时间,并且能够恢复以前的版本。
例如 Stack Exchange 使用了类似的东西。当我改变别人的问题时,有可能发现我改变了它,并且回滚了改变。
考虑到我当前的架构与普通业务应用程序具有大致相同的属性(如下),用于将每个更改存储在数据库中的通用技术是什么?
nvarchar(1000)例如可能有一些,但不是大量的二进制数据,这个直接存储在磁盘上,直接访问,而不是通过 Microsoft SQL filestream,<tl-博士>
我想过以下案例,但我对这些场景没有真正的经验,所以我想听听其他人的意见:
将所有内容存储在同一个表中,按 ID 和版本区分行。IMO,这是非常愚蠢的,迟早会在性能水平上受到伤害。使用这种方法,也不可能为最新项目和版本跟踪设置不同的安全级别。最后,每个查询的编写都会更加复杂。实际上,要访问最新数据,我将被迫按 ID 对所有内容进行分组,并在每个组中检索最新版本。
将最新版本存储在一个表中,并在每次更改时将过时版本复制到另一个模式中的另一个表中。缺陷是每次我们都会存储每个值,即使它没有改变。设置不变值null不是一个解决方案,因为我还必须当值更改为跟踪null或null。
将最新版本存储在一个表中,并将更改的属性列表及其以前的值存储在另一个表中。这似乎有两个缺陷:最重要的一个是,对同一列中不同类型的先前值进行排序的唯一方法是使用binary(max). 第二个是,我相信,在向用户显示以前的版本时,使用这种结构会更加困难。
执行与前两点相同的操作,但将版本存储在单独的数据库中。在性能方面,为了避免通过将以前的版本放在同一数据库中而减慢对最新版本的访问速度可能会很有趣;尽管如此,我认为这是一个过早的优化,只有在有证据表明在同一数据库中拥有旧版本和最新版本是瓶颈时才必须进行优化。
</tl-dr>
¹ 例如,将更改存储到日志文件中是不可接受的,就像对 HTTP 日志所做的那样,并在服务器负载最低的晚上将数据从日志刷新到数据库中。有关不同版本的信息必须立即或几乎立即可用;几秒钟的延迟是可以接受的。
² 信息不是很频繁,只有特定的用户组才能访问,但是,强迫他们等待 30 秒才能显示版本列表是不可接受的。同样,几秒钟的延迟是可以接受的。
我在任何地方都找不到关于此的明确资源,所以希望大师可以在这里给我一个答案。
我有一个非常大的表,我们必须向其中添加一列。聚集索引非常碎片化,我想做一个ALTER INDEX REBUILD清理它。
我通常也做一个 ALTER TABLE REBUILD在更改列时,因为这会清除该操作中的任何指针或拆分。
由于我们谈论的是聚集索引(本质上是表),所以我是否需要同时执行这两项操作?
我的怀疑是ALTER INDEX REBUILD在集群上不会更新所有的东西ALTER TABLE,但我也担心ALTER TABLE不会清理索引碎片。
我有一个有点复杂的 SQL Server 2008 查询(大约 200 行相当密集的 SQL),它没有按照我的需要执行。随着时间的推移,性能从大约 0.5 秒下降到大约 2 秒。
查看执行计划,很明显,通过重新排序连接,可以提高性能。我做到了,它做到了……下降到大约 0.3 秒。现在查询有“OPTION FORCE ORDER”提示,生活很好。
今天我来了,清理数据库。我归档了大约 20% 的行,除了删除行之外,在相关数据库中没有采取任何行动……执行计划完全被控制。它完全错误地判断了某些子树将返回多少行,并且(例如)替换了一个:
<Hash>
Run Code Online (Sandbox Code Playgroud)
和
<NestedLoops Optimized='false' WithUnorderedPrefetch='true'>
Run Code Online (Sandbox Code Playgroud)
现在查询时间从大约 0.3 秒飙升到大约 18 秒。(!) 只是因为我删除了行。如果我删除查询提示,我将回到大约 2 秒的查询时间。更好,但更糟。
将数据库恢复到多个位置和服务器后,我重现了该问题。简单地从每个表中删除大约 20% 的行总是会导致这个问题。
如果您想查看执行计划,请建议我可以发布它们的位置。否则,我已经采样了最令人惊叹的一点。这是基本的错误估计,括号中的数字是(估计:实际)行。
/ Clustered Index Scan (908:7229)
Nested Loops (Inner Join) --<
\ NonClustered Index Seek (1:7229)
Run Code Online (Sandbox Code Playgroud)
请注意,内循环预期扫描 908 行,但扫描 52,258,441。如果它是准确的,这个分支会运行大约 2 毫秒,而不是 12 秒。在删除行之前,这个内部连接估计值仅相差 2 倍,并且作为两个聚集索引的哈希匹配执行。
performance sql-server-2008 sql-server optimization hints query-performance
我有一个 SQL Server 2008 R2 表,其架构结构如下所示:
CREATE TABLE [dbo].[CDSIM_BE]
(
[ID] [bigint] NOT NULL,
[EquipmentID] [varchar](50) NOT NULL,
[SerialNumber] [varchar](50) NULL,
[PyrID] [varchar](50) NULL,
[MeasMode] [varchar](50) NULL,
[ReadTime] [datetime] NOT NULL,
[SubID] [varchar](15) NULL,
[ProbePosition] [float] NULL,
[DataPoint] [int] NULL,
CONSTRAINT [PK_CDSIM_BE]
PRIMARY KEY CLUSTERED ([ID] ASC, [EquipmentID] ASC, [ReadTime] ASC)
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON) ON [MonthlyArchiveScheme9]([ReadTime])
) ON [MonthlyArchiveScheme9]([ReadTime])
CREATE NONCLUSTERED INDEX [idx_CDSIM_BE__SubID_ProbePosition]
ON [dbo].[CDSIM_BE] ([SubID] ASC, …Run Code Online (Sandbox Code Playgroud) performance sql-server optimization execution-plan sql-server-2008-r2 query-performance
添加 7/11问题是由于 MERGE JOIN 期间的索引扫描而发生死锁。在这种情况下,一个事务试图在 FK 父表的整个索引上获得 S 锁,但之前另一个事务在索引的键值上放置了 X 锁。
让我从一个小例子开始(使用来自 70-461 课程的 TSQL2012 DB):
CREATE TABLE [Sales].[Orders](
[orderid] [int] IDENTITY(1,1) NOT NULL,
[custid] [int] NULL,
[empid] [int] NOT NULL,
[shipperid] [int] NOT NULL,
... )
Run Code Online (Sandbox Code Playgroud)
列[custid], [empid], [shipperid]是相应的相关参数[Sales].[Customers], [HR].[Employees], [Sales].[Shippers]。在每种情况下,我们在父表中的引用列上都有一个聚集索引。
ALTER TABLE [Sales].[Orders] WITH CHECK ADD CONSTRAINT [FK_Orders_Customers] FOREIGN KEY([custid]) REFERENCES [Sales].[Customers] ([custid])
ALTER TABLE [Sales].[Orders] WITH CHECK ADD CONSTRAINT [FK_Orders_Employees] FOREIGN KEY([empid]) REFERENCES [HR].[Employees] ([empid])
ALTER TABLE [Sales].[Orders] WITH CHECK ADD CONSTRAINT …Run Code Online (Sandbox Code Playgroud) performance foreign-key deadlock optimization t-sql query-performance
我知道在使用索引或表扫描时,SQL Server 使用统计信息来查看哪个更好。
我有一个有 2000 万行的表。我在 (SnapshotKey, Measure) 和这个查询上有一个索引:
select Measure, SnapshotKey, MeasureBand
from t1
where Measure = 'FinanceFICOScore'
group by Measure, SnapshotKey, MeasureBand
Run Code Online (Sandbox Code Playgroud)
查询返回 500k 行。所以查询只选择了表中 2.5% 的行。
问题是为什么 SQL Server 不使用我拥有的非聚集索引,而是使用表扫描?
统计数据已更新。
值得一提的是,查询性能很好。
CREATE TABLE [t1](
[SnapshotKey] [int] NOT NULL,
[SnapshotDt] [date] NOT NULL,
[Measure] [nvarchar](30) NOT NULL,
[MeasureBand] [nvarchar](30) NOT NULL,
-- and many more fields
) ON [PRIMARY]
Run Code Online (Sandbox Code Playgroud)
表上没有PK,因为它是一个数据仓库。
CREATE NONCLUSTERED INDEX [nci_SnapshotKeyMeasure] ON [t1]
(
[SnapshotKey] ASC,
[Measure] ASC
)
Run Code Online (Sandbox Code Playgroud) index sql-server optimization execution-plan sql-server-2012
将常规列转换为持久计算列会导致此查询无法执行索引查找。为什么?
在多个 SQL Server 版本上进行了测试,包括 2016 SP1 CU1。
问题在于table1, col7。
表和查询是原始版本的部分(和简化)版本。我知道查询可以用不同的方式重写,并且出于某种原因避免了这个问题,但我们需要避免接触代码,为什么table1不能被搜索的问题仍然存在。
正如 Paul White 所展示的(谢谢!),如果强制执行,则搜索可用,所以问题是:为什么优化器不选择搜索,以及我们是否可以做一些不同的事情来使搜索按预期进行,而无需更改代码?
为了澄清有问题的部分,这是错误执行计划中的相关扫描:

假设您有以下表结构:
LogId | ProductId | FromPositionId | ToPositionId | Date | Quantity
-----------------------------------------------------------------------------------
1 | 123 | 0 | 10002 | 2018-01-01 08:10:22 | 5
2 | 123 | 0 | 10003 | 2018-01-03 15:15:10 | 9
3 | 123 | 10002 | 10004 | 2018-01-07 21:08:56 | 3
4 | 123 | 10004 | 0 | 2018-02-09 10:03:23 | 1
Run Code Online (Sandbox Code Playgroud)
FromPositionId并且ToPositionId是股票头寸。某些位置 ID:s 具有特殊含义,例如0。事件 from 或 to0表示库存已创建或删除。From0可能是交货的库存,to0可能是发货的订单。
该表目前包含大约 550 …
长话短说,我们正在使用非常大的人员表中的值更新小型人员表。在最近的测试中,此更新需要大约 5 分钟才能运行。
我们偶然发现了看似最愚蠢的优化方法,但它似乎完美无缺!相同的查询现在可以在不到 2 分钟的时间内运行并完美地产生相同的结果。
这是查询。最后一行被添加为“优化”。为什么查询时间急剧减少?我们错过了什么吗?这会导致将来出现问题吗?
UPDATE smallTbl
SET smallTbl.importantValue = largeTbl.importantValue
FROM smallTableOfPeople smallTbl
JOIN largeTableOfPeople largeTbl
ON largeTbl.birth_date = smallTbl.birthDate
AND DIFFERENCE(TRIM(smallTbl.last_name),TRIM(largeTbl.last_name)) = 4
AND DIFFERENCE(TRIM(smallTbl.first_name),TRIM(largeTbl.first_name)) = 4
WHERE smallTbl.importantValue IS NULL
-- The following line is "the optimization"
AND LEFT(TRIM(largeTbl.last_name), 1) IN ('a','à','á','b','c','d','e','è','é','f','g','h','i','j','k','l','m','n','o','ô','ö','p','q','r','s','t','u','ü','v','w','x','y','z','æ','ä','ø','å')
Run Code Online (Sandbox Code Playgroud)
技术说明:我们知道要测试的字母列表可能需要更多的字母。我们也意识到使用“DIFFERENCE”时明显的误差幅度。
查询计划(常规): https : //www.brentozar.com/pastetheplan/?
id = rypV84y7V 查询计划(带“优化”):https : //www.brentozar.com/pastetheplan/?id=r1aC2my7E
例如,如果我点击 sys 动态视图来选择一个特定的查询计划,我是否能够将该查询计划插入到另一个数据库的计划缓存中,运行相同的查询?(我知道查询经过哈希处理并进行比较以确定何时生成新计划,因此在我的示例中,我将确保查询确实是逐个字符完全相同的。)
optimization ×10
sql-server ×9
performance ×3
deadlock ×1
foreign-key ×1
hints ×1
index ×1
schema ×1
t-sql ×1