介绍
冗余是维护的魔鬼, 是性能优化的天使
常见的冗余有
1. computed column
2. foreign table 的识别字段
维护冗余的方案有很多. 比如 computed column, trigger, view, 甚至在应用层写 event bus.
但不同情况利弊也不同. 还得看场景决定.
我目前使用 computed column 和 trigger 来维护冗余.
对比在应用层维护, 好处是可以直接修改 SQL, 冗余一样可以正常 working (在业务还不稳定的情况下, 直接使用数据库来做信息管理可以提高效率和节约试错成本)
另一个好处是不需要在应用层额外的开发一套维护方案, 要知道 EF core 并没有现成的方案,甚至连 trigger 机制都没有 build-in 的.
Computed Column Same Row
比如 Subtoal, 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
如果超出这个限制, 那么就需要用其它方案了.
Computed Column Cross Table
如果是跨表比如 TotalAmount 要 SUM 的这种.
就要使用 Trigger 监听所有依赖字段, 然后重新跑 Computed 方法
例子:
DROP TRIGGER TR_InvoiceItem_AfterInsert_ForRedundancy_Invoice_TotalAmount; GO CREATE TRIGGER TR_InvoiceItem_AfterInsert_ForRedundancy_Invoice_TotalAmount ON InvoiceItem AFTER INSERT AS IF (ROWCOUNT_BIG() = 0) RETURN; SET NOCOUNT ON; UPDATE Invoice SET TotalAmount = (SELECT SUM(Subtotal) FROM InvoiceItem WHERE InvoiceId = Invoice.InvoiceId) FROM Invoice INNER JOIN inserted ON Invoice.InvoiceId = inserted.InvoiceId; GO DROP TRIGGER TR_InvoiceItem_AfterDelete_ForRedundancy_Invoice_TotalAmount; GO CREATE TRIGGER TR_InvoiceItem_AfterDelete_ForRedundancy_Invoice_TotalAmount ON InvoiceItem AFTER DELETE AS IF (ROWCOUNT_BIG() = 0) RETURN; SET NOCOUNT ON; UPDATE Invoice SET TotalAmount = (SELECT SUM(Subtotal) FROM InvoiceItem WHERE InvoiceId = Invoice.InvoiceId) FROM Invoice INNER JOIN deleted ON Invoice.InvoiceId = deleted.InvoiceId; GO DROP TRIGGER TR_InvoiceItem_AfterUpdate_ForRedundancy_Invoice_TotalAmount; GO CREATE TRIGGER TR_InvoiceItem_AfterUpdate_ForRedundancy_Invoice_TotalAmount ON InvoiceItem AFTER UPDATE AS IF (ROWCOUNT_BIG() = 0) RETURN; SET NOCOUNT ON; UPDATE Invoice SET TotalAmount = (SELECT SUM(Subtotal) FROM InvoiceItem WHERE InvoiceId = Invoice.InvoiceId) FROM Invoice LEFT JOIN deleted ON Invoice.InvoiceId = deleted.InvoiceId LEFT JOIN inserted ON Invoice.InvoiceId = inserted.InvoiceId WHERE (deleted.InvoiceItemId IS NOT NULL OR inserted.InvoiceItemId IS NOT NULL) AND (inserted.Subtotal != deleted.Subtotal OR inserted.InvoiceId != deleted.InvoiceId); GO
Foreign Table 识别字段
比如 Name, Code, Number 之类的. 由于 foreign table 是依靠 Id 作为 key, 而 Id 对业务来说不具备识别能力, 所以一般上会需要一些识别字段
每次 join table 或者识别字段对性能很伤, 所以就有了 Foreign Table 识别字段的冗余.
同样可以使用 Trigger 来维护
例子:
DROP TRIGGER TR_InvoiceItem_AfterInsert_ForRedundancy_InvoiceItem_InvoiceNumber; GO CREATE TRIGGER TR_InvoiceItem_AfterInsert_ForRedundancy_InvoiceItem_InvoiceNumber ON InvoiceItem AFTER INSERT AS IF (ROWCOUNT_BIG() = 0) RETURN; SET NOCOUNT ON; UPDATE InvoiceItem SET InvoiceNumber = Invoice.Number FROM InvoiceItem INNER JOIN inserted ON InvoiceItem.InvoiceItemId = inserted.InvoiceItemId LEFT JOIN Invoice ON InvoiceItem.InvoiceId = Invoice.InvoiceId GO DROP TRIGGER TR_InvoiceItem_AfterUpdate_ForRedundancy_InvoiceItem_InvoiceNumber; GO CREATE TRIGGER TR_InvoiceItem_AfterUpdate_ForRedundancy_InvoiceItem_InvoiceNumber ON InvoiceItem AFTER Update AS IF (ROWCOUNT_BIG() = 0) RETURN; SET NOCOUNT ON; UPDATE InvoiceItem SET InvoiceNumber = Invoice.Number FROM InvoiceItem INNER JOIN inserted ON InvoiceItem.InvoiceItemId = inserted.InvoiceItemId LEFT JOIN deleted ON InvoiceItem.InvoiceItemId = deleted.InvoiceItemId LEFT JOIN Invoice ON InvoiceItem.InvoiceId = Invoice.InvoiceId WHERE inserted.InvoiceId != deleted.InvoiceId; GO UPDATE InvoiceItem SET Qty = Qty + 1 WHERE InvoiceItemId = 1; DROP TRIGGER TR_Invoice_AfterUpdate_ForRedundancy_InvoiceItem_InvoiceNumber; GO CREATE TRIGGER TR_Invoice_AfterUpdate_ForRedundancy_InvoiceItem_InvoiceNumber ON Invoice AFTER UPDATE AS IF (ROWCOUNT_BIG() = 0) RETURN; SET NOCOUNT ON; UPDATE InvoiceItem SET InvoiceNumber = inserted.Number FROM InvoiceItem INNER JOIN inserted ON InvoiceItem.InvoiceId = inserted.InvoiceId LEFT JOIN deleted ON InvoiceItem.InvoiceId = deleted.InvoiceId WHERE inserted.Number != deleted.Number; GO