如何解决SQL Server:在外键上创建索引
我在这里试验的是 DELETE 语句如何在一个非常简单的示例上执行。我目前使用的是 sql Server 2017(我也尝试过 sql Server 2014,结果相似)。
我有两个表:Parent
和 Child
。 Child
有一个指向 Parent (Parent_ID)
的外键。
家长:
Parent_ID Name
-----------------------
1 P1
2 P2
孩子:
Child_ID Parent_ID Data
-----------------------------
1 1 P1C1
2 2 P2C1
3 2 PPPPCCCC
4 2 P2C1
5 2 PPPPCCCC
(around 4 million more rows with Parent_ID=2)
我一直认为在外键(此处Parent_ID
中的Child
)上添加索引是个好主意。但是今天,我在一个有点极端的情况下尝试了 DELETE 的行为 - 但我相信这种情况可能会发生在现实生活中 - (子表中有 4 百万行 Parent_ID=2,只有一行 Parent_ID= 1).
如果我尝试删除 Parent_ID = 1 的行,它看起来不错:它足够快,使用了索引,逻辑读取量似乎很好(12 个逻辑读取:我我不是专家,不知道这么少量的数据是否真的可以)。
现在我不明白(也不喜欢):
我尝试删除 Child 中 Parent_ID=2 的所有记录:
BEGIN TRAN
DELETE FROM child
WHERE parent_id = 2
ROLLBACK TRAN
表“孩子”。扫描计数1,逻辑读38486782,物理读0,预读0,lob逻辑读0,lob物理读0,lob预读0。
38486782 逻辑读取...不是很大吗?我已经尝试更新统计数据以确保。
UPDATE STATISTICS Child WITH FULLSCAN
然后再次运行我的查询 => 相同的结果。可能是 IX_Child_Parent_ID 上的索引删除问题?
禁用外键上的索引后,事情变得更好了:
表“孩子”。扫描计数1,逻辑读202233,物理读0,预读0,lob逻辑读0,lob物理读0,lob预读0。
注意:sql Server 建议为 FK 创建索引。
202233 逻辑读取听起来好多了……至少对于 Parent_Id=2 的特定情况。
问题是:为什么 sql Server 使用索引而不选择聚集索引扫描方法,当它知道Parent_ID = 2 大约有 4 000 000 行时?或者它可能不知道?统计数据不是应该“帮助”sql Server 了解此类信息吗?
我可能遗漏了一些东西。
(我已经仔细检查过,在创建索引后,统计数据 - 似乎 - OK:
解决方法
出于删除的目的和您拥有的数据的基数,如您所见,parent_id
上的索引没有用处。
如果您知道需要删除 99% 的行,那么出于多种原因,这样做是非常低效的,尤其是事务日志的增长。
您执行的每个语句都是原子的,并且是它自己的隐式事务,如果删除在中途失败,即断电,SQL Server 需要能够回滚未完成的删除,为此它使用事务日志,所以所有被删除的行都会命中事务日志。
在这种情况下,将要保留的行插入到新表中,删除原始表,然后将新表重命名为原始表,性能会更高;您还可以编写来自原始表的索引/约束的脚本并将它们应用到新表。
如果删除大部分行但少于表的 50%,其他建议是将作业拆分为批次并一次删除
通常可以帮助在表上创建一个 View 选择要删除的 top n 行,按特定的 键 排序,然后从视图中删除。
,也许我在这里找到了答案:https://stackoverflow.com/a/3650886。
看起来这是预期的行为,问题实际上是更新索引(在我的情况下为 IX_Child_Parent_ID)。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。