性能考虑:在多个表中分散行与将所有行集中在一个表中

Posted

技术标签:

【中文标题】性能考虑:在多个表中分散行与将所有行集中在一个表中【英文标题】:Performance consideration: Spread rows in multiple tables vs concentrate all rows in one table 【发布时间】:2009-07-17 09:36:28 【问题描述】:

嗨。

我需要在 SQL DB 中记录有关应用程序中执行的每个步骤的信息。 有某些表,我希望日志应该与: 产品 - 应在创建产品时记录更改等。 顺序 - 同上 运输 - 相同 等等等等等等。

需要经常检索数据。

我对如何做到这一点没有什么想法:

    有一个日志表,其中包含所有这些表的列,然后当我想在 UI 中表示某个产品的数据时,会从 Log 中选择 *,其中 LogId = Product.ProductId。 我知道有很多列可能会很有趣,但我有这种感觉,性能会更好。另一方面,此表中会有大量行。 每种日志类型都有很多日志表(ProductLogs、OrderLogs 等)在行数较少的表中搜索时更快(我错了吗?)。 根据声明号。 1,我可以做第二个多对一表,它将具有 LogId、TableNameId 和 RowId 列,并将日志行引用到数据库中的许多表行,而不是有一个 UDF 来检索数据(例如日志 id 234属于 CustomerId 345 处的表 Customer 和 productId = RowId) 的 Product 表;我认为这是最好的方法,但同样,可能会有大量的行,它会减慢搜索速度吗?或者这应该是怎么做的,怎么说呢?...

以上列表中的第 3 位示例:

CREATE TABLE [dbo].[Log](
    [LogId] [int] IDENTITY(1,1) NOT NULL,
    [UserId] [int] NULL,
    [Description] [varchar](1024) NOT NULL,
 CONSTRAINT [PK_Log] PRIMARY KEY CLUSTERED 
(
    [LogId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO
ALTER TABLE [dbo].[Log]  WITH CHECK ADD  CONSTRAINT [FK_Log_Table] FOREIGN KEY([UserId])
REFERENCES [dbo].[Table] ([TableId])
GO
ALTER TABLE [dbo].[Log] CHECK CONSTRAINT [FK_Log_Table]
---------------------------------------------------------------------
CREATE TABLE [dbo].[LogReference](
    [LogId] [int] NOT NULL,
    [TableName] [varchar](32) NOT NULL,
    [RowId] [int] NOT NULL,
 CONSTRAINT [PK_LogReference] PRIMARY KEY CLUSTERED 
(
    [LogId] ASC,
    [TableName] ASC,
    [RowId] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[LogReference]  WITH CHECK ADD  CONSTRAINT [FK_LogReference_Log] FOREIGN KEY([LogId])
REFERENCES [dbo].[Log] ([LogId])
GO
ALTER TABLE [dbo].[LogReference] CHECK CONSTRAINT [FK_LogReference_Log]
---------------------------------------------------------------------
CREATE FUNCTION GetLog
(   
    @TableName varchar(32),
    @RowId int
)
RETURNS 
@Log TABLE
(       
    LogId int not null,
    UserId int not null,
    Description varchar(1024) not null
)
AS
BEGIN

INSERT INTO @Log
SELECT     [Log].LogId, [Log].UserId, [Log].Description
FROM         [Log] INNER JOIN
                      LogReference ON [Log].LogId = LogReference.LogId
WHERE     (LogReference.TableName = @TableName) AND (LogReference.RowId = @RowId)
    RETURN 
END
GO

【问题讨论】:

【参考方案1】:

小心预优化数据库。大多数数据库都相当快并且有些复杂。您想先进行效率测试。

第二次将所有内容放在一个表中,您想要的结果更有可能在缓存中,这将极大地提高性能。不幸的是,它也使您更有可能必须搜索一张巨大的桌子才能找到您要找的东西。这可以通过索引部分解决,但索引并不是免费的(它们使编写成本更高,一方面)。

我的建议是进行测试,看看性能是否真的很重要,然后测试不同的场景,看看哪个是最快的。

【讨论】:

我对开发时间有点严格,您认为最推荐的方式是什么? 最干净和最有效的方法是列表中的第 3 位,问题是否会太慢。【参考方案2】:

如果您谈论的是大量数据(数百万行以上),那么您将受益于使用不同的表来存储它们。

例如基本示例 5000 万条日志条目,假设有 5 种不同“类型”的日志表 拥有 5 x 1000 万行表比 1 x 5000 万行表更好

单个表的 INSERT 性能会更好 - 每个表上的索引会更小,因此作为插入操作的一部分更新/维护会更快/更容易

单个表的 READ 性能会更好 - 要查询的数据更少,要遍历的索引更小。此外,听起来您需要存储一个额外的列来识别记录的日志条目类型(产品、运输......)

在较小的表上进行维护的痛苦较小(统计信息、索引碎片整理/重建等)

本质上,这是关于分区数据。从 SQL 2005 开始,它内置了对分区的支持(请参阅here),但您需要企业版,它基本上允许您将数据分区到一个表中以提高性能(例如,您将拥有一个日志表,然后定义其中的数据如何分区)

我最近听了一位 eBay 架构师的采访,他强调了在需要性能和可扩展性时分区的重要性,根据我的经验,我非常同意。

【讨论】:

【参考方案3】:

出于以下几个原因,我肯定会选择选项 3:

数据应该在表的字段中,而不是表名(选项 2)或字段名(选项 1)。这样,数据库变得更容易使用和维护。

较窄的表格通常表现更好。行数对性能的影响小于字段数。

如果每个表都有一个字段(选项 1),当只有少数表受操作影响时,您可能会得到很多空字段。

【讨论】:

你看,我同意你的观点,这个问题特别是关于插入和搜索,我不在乎检索会很慢。【参考方案4】:

尝试以某种方式实现您的数据访问层,以便您可以在需要时从一种数据库模型更改为另一种 - 这样您只需选择一个并担心以后会影响性能。

如果不进行一些性能测试并准确了解您将要获得的负载类型,将很难对其进行优化,因为性能取决于许多因素,例如读取次数、写入次数,以及读写是否可能发生冲突并导致锁定。

顺便说一句,我更喜欢选项 1 - 它最简单,您可以进行许多调整来帮助解决您可能遇到的各种问题。

【讨论】:

以上是关于性能考虑:在多个表中分散行与将所有行集中在一个表中的主要内容,如果未能解决你的问题,请参考以下文章

属性表模式与将所有属性存储在 json 列中 [重复]

将活动行存储在位列中与将逻辑存储在视图中

多个分区子查询的性能

当查询返回多个 min(count) 数据时,如何从不同表中选择所有行

快速选择另一个表中具有“1 个或多个”匹配行的所有行

如何在一个表的键中分配多个值?