SQL Server 冗余维护
介绍
冗余是维护的魔鬼, 是性能优化的天使
常见的冗余有
1. computed column
2. principal 的识别字段
3. cross computed
4. cascade soft delete
维护冗余的方案有很多. 比如 computed column, trigger, view, 甚至在应用层写 event bus.
但不同情况利弊也不同. 还得看场景决定.
我目前使用 computed column 和 trigger 来维护冗余.
对比在应用层维护, 好处是可以直接修改 SQL, 冗余一样可以正常 working (在业务还不稳定的情况下, 直接使用数据库来做信息管理可以提高效率和节约试错成本)
另一个好处是不需要在应用层额外的开发一套维护方案, 要知道 EF core 并没有现成的方案,甚至连 trigger 机制都没有 build-in 的.
Computed Column Same Row
比如 Subtotal, TotalAmount 这类的字段.
比较简单的 computed column 是依赖同一个 row 里面的字段, 比如 FullName, Subtotal
ALTER TABLE InvoiceItem DROP COLUMN Subtotal;
GO
ALTER TABLE InvoiceItem ADD Subtotal as (CAST(Qty as DECIMAL(19)) * UnitPrice) PERSISTED NOT NULL;
GO
用普通的 computed column 就可以解决了, 只能依赖同行, 而且依赖的字段不可以是 computed column
Cross Table Computed Column
如果需要跨表, 比如 TotalAmount 要 SUM 子表.
就要使用 Trigger 监听所有依赖字段, 然后重新跑 Computed 方法.
例子:
GO
CREATE OR ALTER TRIGGER TR_Contract_AfterInsert_ForCrossComputed_Project_ProjectBiddingCost ON [Contract]
AFTER INSERT
AS
IF (ROWCOUNT_BIG() = 0) RETURN;
SET NOCOUNT ON;
UPDATE [ParentTable] SET [ProjectBiddingCost] = ISNULL(
(SELECT SUM([ContractBiddingCost]) FROM [Contract]
WHERE [ProjectId] = [ParentTable].[ProjectId] AND (([Deleted] = 0))), 0
)
FROM [Project] AS [ParentTable]
INNER JOIN inserted ON [ParentTable].[ProjectId] = inserted.[ProjectId];
GO GO
CREATE OR ALTER TRIGGER TR_Contract_AfterDelete_ForCrossComputed_Project_ProjectBiddingCost ON [Contract]
AFTER DELETE
AS
IF (ROWCOUNT_BIG() = 0) RETURN;
SET NOCOUNT ON;
UPDATE [ParentTable] SET [ProjectBiddingCost] = ISNULL(
(SELECT SUM([ContractBiddingCost]) FROM [Contract]
WHERE [ProjectId] = [ParentTable].[ProjectId] AND (([Deleted] = 0))), 0
)
FROM [Project] AS [ParentTable]
INNER JOIN deleted ON [ParentTable].[ProjectId] = deleted.[ProjectId];
GO GO
CREATE OR ALTER TRIGGER TR_Contract_AfterUpdate_ForCrossComputed_Project_ProjectBiddingCost ON [Contract]
AFTER UPDATE
AS
IF (ROWCOUNT_BIG() = 0) RETURN;
SET NOCOUNT ON;
UPDATE [ParentTable] SET [ProjectBiddingCost] = ISNULL(
(SELECT SUM([ContractBiddingCost]) FROM [Contract]
WHERE [ProjectId] = [ParentTable].[ProjectId] AND (([Deleted] = 0))), 0
)
FROM deleted
INNER JOIN inserted ON deleted.[ContractId] = inserted.[ContractId]
INNER JOIN [Project] AS [ParentTable] ON deleted.[ProjectId] = [ParentTable].[ProjectId] OR inserted.[ProjectId] = [ParentTable].[ProjectId]
WHERE (((deleted.[Deleted] <> inserted.[Deleted]) OR (deleted.[Deleted] IS NULL OR inserted.[Deleted] IS NULL)) AND (deleted.[Deleted] IS NOT NULL OR inserted.[Deleted] IS NOT NULL))
OR (((deleted.[ProjectId] <> inserted.[ProjectId]) OR (deleted.[ProjectId] IS NULL OR inserted.[ProjectId] IS NULL)) AND (deleted.[ProjectId] IS NOT NULL OR inserted.[ProjectId] IS NOT NULL))
OR (((deleted.[ContractBiddingCost] <> inserted.[ContractBiddingCost]) OR (deleted.[ContractBiddingCost] IS NULL OR inserted.[ContractBiddingCost] IS NULL)) AND (deleted.[ContractBiddingCost] IS NOT NULL OR inserted.[ContractBiddingCost] IS NOT NULL));
GO
Principal Table 识别字段
比如 Name, Code, Number 之类的. 由于 foreign table 是依靠 Id 作为 foreign key, 而 Id 对业务来说不具备识别能力, 所以一般上会需要一些识别字段
每次 join table 获取识别字段对性能很伤, 语句也不好了, 所以就有了 Principal 识别字段的冗余.
同样可以使用 Trigger 来维护
例子:
GO
CREATE OR ALTER TRIGGER [TR_PaymentInvoice_AfterInsert_ForPrincipalProperty_PaymentInvoice_ProjectNumber] ON [PaymentInvoice]
AFTER INSERT
AS
IF (ROWCOUNT_BIG() = 0) RETURN;
SET NOCOUNT ON; UPDATE [PaymentInvoice] SET [ProjectNumber] = [PurchaseOrder].[ProjectNumber]
FROM [PaymentInvoice]
INNER JOIN inserted ON [PaymentInvoice].[PurchaseOrderId] = [inserted].[PurchaseOrderId]
INNER JOIN [PurchaseOrder] ON [PaymentInvoice].[PurchaseOrderId] = [PurchaseOrder].[PurchaseOrderId];
GO GO
CREATE OR ALTER TRIGGER [TR_PaymentInvoice_AfterUpdate_ForPrincipalProperty_PaymentInvoice_ProjectNumber] ON [PaymentInvoice]
AFTER Update
AS
IF (ROWCOUNT_BIG() = 0) RETURN;
SET NOCOUNT ON; UPDATE [PaymentInvoice] SET [ProjectNumber] = [PurchaseOrder].[ProjectNumber]
FROM deleted
INNER JOIN inserted ON deleted.[PaymentInvoiceId] = inserted.[PaymentInvoiceId]
INNER JOIN [PurchaseOrder] ON [inserted].[PurchaseOrderId] = [PurchaseOrder].[PurchaseOrderId]
WHERE (((deleted.[PurchaseOrderId] <> inserted.[PurchaseOrderId]) OR (deleted.[PurchaseOrderId] IS NULL OR inserted.[PurchaseOrderId] IS NULL)) AND (deleted.[PurchaseOrderId] IS NOT NULL OR inserted.[PurchaseOrderId] IS NOT NULL));
GO GO
CREATE OR ALTER TRIGGER [TR_PurchaseOrder_AfterUpdate_ForPrincipalProperty_PaymentInvoice_ProjectNumber] ON [PurchaseOrder]
AFTER UPDATE
AS
IF (ROWCOUNT_BIG() = 0) RETURN;
SET NOCOUNT ON; UPDATE [PaymentInvoice] SET [ProjectNumber] = inserted.[ProjectNumber]
FROM deleted
INNER JOIN inserted ON deleted.[PurchaseOrderId] = inserted.[PurchaseOrderId]
INNER JOIN [PaymentInvoice] ON [inserted].[PurchaseOrderId] = [PaymentInvoice].[PurchaseOrderId]
WHERE (((deleted.[ProjectNumber] <> inserted.[ProjectNumber]) OR (deleted.[ProjectNumber] IS NULL OR inserted.[ProjectNumber] IS NULL)) AND (deleted.[ProjectNumber] IS NOT NULL OR inserted.[ProjectNumber] IS NOT NULL));
GO
Cascade soft delete
SQL Server 支持 Cascade delete, 但如果希望 soft delete 就没有 build-in 支持了.
用 trigger 也是可以解决
例子:
GO
CREATE OR ALTER TRIGGER [TR_TradeItem_AfterUpdate_ForCascadeSoftDelete_PurchaseRequisition] ON [TradeItem]
AFTER UPDATE
AS
IF (ROWCOUNT_BIG() = 0) RETURN;
SET NOCOUNT ON; UPDATE [PurchaseRequisition]
SET [DeletedBy] =
CASE
WHEN deleted.[DateDeleted] IS NULL AND inserted.[DateDeleted] IS NOT NULL
THEN
CASE
WHEN [PurchaseRequisition].[Deleted] = 1 THEN [PurchaseRequisition].[DeletedBy]
ELSE inserted.DeletedBy
END
ELSE
CASE
WHEN [PurchaseRequisition].[DateDeleted] = deleted.[DateDeleted] THEN NULL
ELSE [PurchaseRequisition].[DeletedBy]
END
END,
DateDeleted =
CASE
WHEN deleted.[DateDeleted] IS NULL AND inserted.[DateDeleted] IS NOT NULL
THEN
CASE
WHEN [PurchaseRequisition].[Deleted] = 1 THEN [PurchaseRequisition].[DateDeleted]
ELSE inserted.[DateDeleted]
END
ELSE
CASE
WHEN [PurchaseRequisition].[DateDeleted] = deleted.[DateDeleted] THEN NULL
ELSE [PurchaseRequisition].[DateDeleted]
END
END
FROM deleted
INNER JOIN inserted
ON deleted.[TradeItemId] = inserted.[TradeItemId]
INNER JOIN [PurchaseRequisition] ON inserted.[TradeItemId] = [PurchaseRequisition].[TradeItemId]
WHERE (((deleted.[DateDeleted] <> inserted.[DateDeleted]) OR (deleted.[DateDeleted] IS NULL OR inserted.[DateDeleted] IS NULL)) AND (deleted.[DateDeleted] IS NOT NULL OR inserted.[DateDeleted] IS NOT NULL));
GO
SQL Server 冗余维护的更多相关文章
- SQL Server 索引维护(1)——系统常见的索引问题
前言: 在很多系统中,比如本人目前管理的数据库,索引经常被滥用,甚至使用DTA(数据库引擎优化顾问)来成批创建索引(DTA目前个人认为它的真正用处应该是在发现缺失的统计信息,在以前的项目中,用过一次D ...
- SQL Server 日常维护经典应用
SQL Server日常维护常用的一些脚本整理. 1.sql server开启clr权限: GO RECONFIGURE GO ALTER DATABASE HWMESTC SET TRUSTWORT ...
- SQL Server 索引维护:系统常见的索引问题
在很多系统中,比如本人目前管理的数据库,索引经常被滥用,甚至使用DTA(数据库引擎优化顾问)来成批创建索引(DTA目前个人认为它的真正用处应该是在发现缺失的统计信息,在以前的项目中,用过一次DTA,里 ...
- SQL Server 索引维护(1)——如何获取索引使用情况
前言: 在前面一文中,已经提到了三类常见的索引问题,那么问题来了,当系统出现这些问题时,该如何应对? 简单而言,需要分析现有系统的行为,然后针对性地对索引进行处理: 对于索引不足的情况:检查缺少索引的 ...
- SQL Server索引维护
索引维护的两个重要方面是索引碎片和统计信息. 一:索引碎片 降低碎片的产生,当索引上的页不在具有物理连续性时,就会产生碎片,下面的情景会产生碎片: INSERT操作.UPDATE操作.DBCC SHR ...
- SQL Server 索引维护sql语句
使用以下脚本查看数据库索引碎片的大小情况: 复制代码代码如下: DBCC SHOWCONTIG WITH FAST, TABLERESULTS, ALL_INDEXES, NO_INFOMSGS 以 ...
- SQL Server 日常维护--查询当前正在执行的语句、死锁、堵塞
查询当前正在执行的语句: SELECT der.[session_id],der.[blocking_session_id], sp.lastwaittype,sp.hostname,sp.progr ...
- SQL SERVER 索引维护
-- 全数据库索引重建 DECLARE @name varchar(100)DECLARE authors_cursor CURSOR FOR Select [name] from sysobject ...
- SQL Server调优系列进阶篇(如何维护数据库索引)
前言 上一篇我们研究了如何利用索引在数据库里面调优,简要的介绍了索引的原理,更重要的分析了如何选择索引以及索引的利弊项,有兴趣的可以点击查看. 本篇延续上一篇的内容,继续分析索引这块,侧重索引项的日常 ...
- SQL Server调优系列进阶篇 - 如何维护数据库索引
前言 上一篇我们研究了如何利用索引在数据库里面调优,简要的介绍了索引的原理,更重要的分析了如何选择索引以及索引的利弊项,有兴趣的可以点击查看. 本篇延续上一篇的内容,继续分析索引这块,侧重索引项的日常 ...
随机推荐
- FPGA CFGBVS 管脚接法
说明 新设计了1个KU040 FPGA板子,回来之后接上JTAG FPGA不识别.做如下检查: 1.电源测试点均正常: 2.查看贴片是否有漏焊,检查无异常,设计上NC的才NC: 3.反复检查JTAG接 ...
- 使用PHP实现字符串的上标和下标,比如:M²和Log₂FC
要在PHP中实现字符串的上标和下标效果,并直接在命令行或网页中正确显示,你可以分别使用Unicode转义序列或HTML实体来表示上标(UPER)和下标(SUB)字符.对于打印到网页的情况,可以使用HT ...
- 基于Java+SpringBoot+vue+element助农平台设计和实现
\n文末获取源码联系 感兴趣的可以先收藏起来,大家在毕设选题,项目以及论文编写等相关问题都可以给我加好友咨询 系统介绍: 随着互联网大趋势的到来,社会的方方面面,各行各业都在考虑利用互联网作为媒介将自 ...
- [oeasy]python0084_扩展BCD_EBCDIC_ibm的发家史
编码进化 回忆上次内容 上次 回顾了 数字 进入二进制世界的 过程 采用的编码 是 BCD Binary Coded Decimal 也叫8421码 十进制数的 二进制形态 数字的 输出形式 辉光管 ...
- C# Expression详解(高级)
LINQ在本地查询IEnumerbale主要是用委托来作为传参,而解析型查询IQueryable则用Expression来作为传参: public static IEnumerable<T> ...
- C# EPPlus帮助类(EPPlusExcelHelper)
public class EPPlusExcelHelper : IDisposable { public ExcelPackage ExcelPackage { get; private set; ...
- 有向图_节点间路径路径--python数据结构
字典创建有向图,查找图节点之间的路径,最短路径,所有路径 """ 参考文档: https://www.python.org/doc/essays/graphs/ &quo ...
- JavaScript小面试~数组相关的方法和运用(学习笔记)
1,稀疏数组 稀疏数组是指数组中的某个下标未给出值或某个下标的值被删除.例如: let arrayOne=['xiaozi',,12,,true,23] let arrayTwo=[1,2,3,3,4 ...
- c++17 structure binding test
1 /*test for struct binding*/ 2 3 #include <string> 4 #include <iostream> 5 using namesp ...
- StringBuilder,一种可变的string
StringBuilder 是 Java 中用于操作字符串的可变对象.它允许在字符串中进行修改.添加.删除字符等操作,而不会像普通的字符串操作(例如使用 String 类)那样产生新的字符串对象.这种 ...