介绍

冗余是维护的魔鬼, 是性能优化的天使

常见的冗余有

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 冗余维护的更多相关文章

  1. SQL Server 索引维护(1)——系统常见的索引问题

    前言: 在很多系统中,比如本人目前管理的数据库,索引经常被滥用,甚至使用DTA(数据库引擎优化顾问)来成批创建索引(DTA目前个人认为它的真正用处应该是在发现缺失的统计信息,在以前的项目中,用过一次D ...

  2. SQL Server 日常维护经典应用

    SQL Server日常维护常用的一些脚本整理. 1.sql server开启clr权限: GO RECONFIGURE GO ALTER DATABASE HWMESTC SET TRUSTWORT ...

  3. SQL Server 索引维护:系统常见的索引问题

    在很多系统中,比如本人目前管理的数据库,索引经常被滥用,甚至使用DTA(数据库引擎优化顾问)来成批创建索引(DTA目前个人认为它的真正用处应该是在发现缺失的统计信息,在以前的项目中,用过一次DTA,里 ...

  4. SQL Server 索引维护(1)——如何获取索引使用情况

    前言: 在前面一文中,已经提到了三类常见的索引问题,那么问题来了,当系统出现这些问题时,该如何应对? 简单而言,需要分析现有系统的行为,然后针对性地对索引进行处理: 对于索引不足的情况:检查缺少索引的 ...

  5. SQL Server索引维护

    索引维护的两个重要方面是索引碎片和统计信息. 一:索引碎片 降低碎片的产生,当索引上的页不在具有物理连续性时,就会产生碎片,下面的情景会产生碎片: INSERT操作.UPDATE操作.DBCC SHR ...

  6. SQL Server 索引维护sql语句

    使用以下脚本查看数据库索引碎片的大小情况: 复制代码代码如下: DBCC SHOWCONTIG WITH FAST, TABLERESULTS, ALL_INDEXES, NO_INFOMSGS  以 ...

  7. SQL Server 日常维护--查询当前正在执行的语句、死锁、堵塞

    查询当前正在执行的语句: SELECT der.[session_id],der.[blocking_session_id], sp.lastwaittype,sp.hostname,sp.progr ...

  8. SQL SERVER 索引维护

    -- 全数据库索引重建 DECLARE @name varchar(100)DECLARE authors_cursor CURSOR FOR Select [name] from sysobject ...

  9. SQL Server调优系列进阶篇(如何维护数据库索引)

    前言 上一篇我们研究了如何利用索引在数据库里面调优,简要的介绍了索引的原理,更重要的分析了如何选择索引以及索引的利弊项,有兴趣的可以点击查看. 本篇延续上一篇的内容,继续分析索引这块,侧重索引项的日常 ...

  10. SQL Server调优系列进阶篇 - 如何维护数据库索引

    前言 上一篇我们研究了如何利用索引在数据库里面调优,简要的介绍了索引的原理,更重要的分析了如何选择索引以及索引的利弊项,有兴趣的可以点击查看. 本篇延续上一篇的内容,继续分析索引这块,侧重索引项的日常 ...

随机推荐

  1. [oeasy]python0106 七段数码管_显示字母_BP机

    七位数码管进化 回忆上次内容 上次回顾了 7-seg 七位数码管 可以显示数字 甚至是十六进制数字   ​   添加图片注释,不超过 140 字(可选)   能否让 七位数码管 将26个字母 全部都显 ...

  2. oeasy教您玩转vim - 6 - # 保存修改

    另存与保存 回忆上节课内容 我们上次进入了插入模式 从正常模式,按<kbd>i</kbd>,进插入模式 从插入模式,按<kbd>ctrl</kbd>+& ...

  3. odoo 为form表单视图添加chatter功能

    实践环境 Odoo 14.0-20221212 (Community Edition) 需求描述 如图,给表单新增一个类似聊天的窗口,当记录一些表单活动(本例为自动记录当前记录状态变化) 需求实现 模 ...

  4. python项目位置迁移后,虚拟环境无法使用

    一.虚拟环境无法使用问题 修改虚拟环境目录/pyvenv.cfg文件中的路径 建议以下配置全部替换一下 修改虚拟环境目录/scripts/activate.bat文件中** VIRTUAL_ENV参数 ...

  5. 浅谈 I/O 与 I/O 多路复用

    1.基础知识 网络编程里常听到阻塞IO.非阻塞IO.同步IO.异步IO等概念,总听别人聊不如自己下来钻研一下.不过,搞清楚这些概念之前,还得先回顾一些基础的概念. 下面说的都是Linux环境下,跟Wi ...

  6. 记hashmap

    hashmap是map接口的一个实现类,在同步的情况下hashmap的性能是比较好的 hashmap就是一个kv键值对的集合,将数值散列均匀的存储在哈希表中.插入方法为map.put(k,v),读取方 ...

  7. RHCA cl210 016 流表 overlay

    Overlay网络是建立在Underlay网络上的逻辑网络 underlay br-int 之间建立隧道 数据流量还是从eth1出去 只有vlan20 是geneve隧道.只有租户网络有子网,子网需要 ...

  8. RPA+ddddocr识别图片验证码

    上篇我们已经使用python识别了验证码,接下来用RPA接收python识别验证码结果进行登录 1.RPA首先安装python包 ddddocr 2.RPA新建一个python模块 编写代码 impo ...

  9. web3 产品介绍:Mirror.xyz是一个创新的去中心化出版平台

    Mirror.xyz是一个创新的去中心化出版平台,它使作者能够创建.发布和管理自己的内容,并与读者建立直接的经济联系.在本文中,我们将介绍Mirror.xyz的主要特点.功能以及如何使用它来发布和消费 ...

  10. 【Vue】HutoolExcel导出

    最近写Excel导出功能,发现需求有点复杂,这里整理一下思路和解决方案 一.需求背景: 老系统改造,功能和代码是现成的,预览了一下内容: 第一个是有特定样式,比如首行标题,以及红色的列名称 第二个,导 ...