介绍

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

常见的冗余有

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]python0020换行字符_feed_line_lf_反斜杠n_B语言_安徒生童话

    ​ 换行字符 回忆上次内容 struct包可以让我们使用封包格式 把数字封包到字节里 pack函数负责封包 unpack函数负责解封 我们通过封到不同的字节状态 遍历了一次ascii码 ​ 编辑 还是 ...

  2. [oeasy]python0011_ 字符序号_ordinal_ord

    ​ 序号(ordinal) 回忆上次内容 ​hello world​​ 不是从来就有的 来自于​​unix​​和​​c​​ 虽然我们今天有各种先进的学习手段 最早的高级语言学习是从最早的那张打字机用纸 ...

  3. 只会建数据库怎么写API?database2api 能帮到你!

    database2api 意为 DataBase to API,即只要有数据库,就可以生成开放 API. database2api 是一款强大而便捷的工具,主要功能是依据现有的数据库自动生成开放的 A ...

  4. BI 工具如何助力市政设计公司实现数字化转型?

    一.前言 近年来,国家出台多个政策文件来鼓励和发展数字化和智能化,如<十四五规划>提出要推进产业数字化转型.<交通强国建设纲要>提出要大力发展智慧交通.上海市发布的<关于 ...

  5. Python列表、元组、字典和集合的用法

    1.列表 标志符号是[],元素可以修改.删除和新增 1.1 提取元素(索引从0开始计算) testList=['A','B','C',1,'D'] print(testList[1]) #打印索引区间 ...

  6. Tomcat日志信息有乱码的处理方法

    1.问题描述 1.1.Idea中的tomcat日志有乱码 1.2.直接启动tomcat的日志有乱码 1.3.原因分析 问题是由于tomcat使用的编码和操作系统使用的编码不一致导致: Windows1 ...

  7. mujoco_py 运行example报错:ERROR: GLEW initalization error: Missing GL version ——— 解决方法

    mujoco的安装与mujoco_py的安装参见: https://www.cnblogs.com/devilmaycry812839668/p/16004320.html mujoco_py安装成功 ...

  8. 再探 游戏 《 2048 》 —— AI方法—— 缘起、缘灭(7) —— Python版本实现的《2048》游戏的TDL算法

    <2048>游戏在线试玩地址: https://play2048.co/ 如何解决<2048>游戏源于外网的一个讨论帖子,而这个帖子则是讨论如何解决该游戏的最早开始,可谓是&q ...

  9. python调用第三方java包实例

    先看结果: 对于python与java的互调,我一开始是用的py4j,但是后来发现在使用方法的时候,不知道如何在python中导入jar包,然后网上的资料也比较少.后来想不出来办法,又看到有Jpype ...

  10. 告别卡顿,畅享GitHub:国内开发者必看的五大加速访问与下载技巧

    告别卡顿,畅享GitHub:国内开发者必看的五大加速访问与下载技巧 本文介绍了五种加速在国内访问和下载 GitHub 的方法,包括:使用 Gitee 平台加速克隆代码.修改 hosts 文件.使用油猴 ...