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调优系列进阶篇 - 如何维护数据库索引
前言 上一篇我们研究了如何利用索引在数据库里面调优,简要的介绍了索引的原理,更重要的分析了如何选择索引以及索引的利弊项,有兴趣的可以点击查看. 本篇延续上一篇的内容,继续分析索引这块,侧重索引项的日常 ...
随机推荐
- [oeasy]python0007_ print函数_字符串_display_电传打字机_程序员的浪漫
你好世界 回忆上次内容 上次 想输出 Hello world! 据说是程序猿的浪漫 键盘按键 作用 ↑ 上一条指令 ↓ 下一条指令 ← 光标 向左移动 一格 → 光标 向右移动 一格 ctrl + ...
- vue中使用xlsx导出excel文件
俗话说,前人栽树,后人乘凉,感谢强人封装好的xlsx,直接使用就可以了.这是网上找到的,也不知道原作者是不是这位博主,先贴出来吧: https://www.cnblogs.com/boylxx/p/1 ...
- 关于failed to load resource 问题的处理
问题: c++做插件,写了一个native class,继承于ue的类ActorComponent,而蓝图里也继承了这个c++ class,都在插件里,每次打开的时候就有这个错误: 之前的解决办法,复 ...
- vue项目使用elementUI的el-upload组件实现图片上传和文件上传的公共组件封装
图片上传: <template> <div class="upload-pic"> <el-upload class="upload-dem ...
- 如何在本地修改Hosts文件设置域名访问?
网站在未上线的时候,我们一般会在本地搭建Web环境并安装WordPress来设计页面.测试插件.数据备份.网站搬家. 为了能够在本地通过域名访问网站,就需要在修改hosts文件来绑定域名,方法如下: ...
- Asp.Net Core之Identity源码学习
什么是Identity ASP.NET Identity是构建核心 Web 应用程序(ASP.NET.登录和用户数据)的成员系统.ASP.NET核心标识允许您向应用程序添加登录功能,并可以轻松自定义有 ...
- 斯坦福AI团队被质疑抄袭国产大模型
原文地址: https://mbd.baidu.com/newspage/data/landingsuper?context={"nid"%3A"news_8882699 ...
- 很好用的python游戏环境:强化学习算法走迷宫游戏环境(导航问题 navigation):分享一个python语言的迷宫游戏环境
项目的GitHub地址(作者:莫凡): https://github.com/MorvanZhou/mmaze 运行的示例代码: import mmaze start = (0, 0) end = ( ...
- tmux使用教程:终端神器tmux:多任务管理大师
文字版教程: 阮一峰 Tmux 使用教程 视频教程: 终端神器tmux:多任务管理大师
- 《Python数据可视化之matplotlib实践》 源码 第四篇 扩展 第十章
图 10.1 import matplotlib.pyplot as plt import numpy as np plt.axes([0.1, 0.7, 0.3, 0.3], frameon=Tru ...