加入100张桌子

时间:2013-02-08 19:53:35

标签: sql sql-server sql-server-2008 join normalization

假设我有一个主表,它有100列引用(作为外键)到100个表(包含主键)。

整套信息需要加入这100张桌子。加入如此多的表肯定是一个性能问题。希望我们可以期望任何用户都希望在查询中请求包含来自不超过5-7个表(在这100个表中)中的值的一堆数据,这些查询将条件(在查询的WHERE部分中)放在大约3-4张桌子(其中100张)。不同的查询具有用于生成查询的“SELECT”部分并将条件放在“WHERE”中的表的不同组合。但是,同样,每个SELECT都需要5-7个表,每个WHERE都需要3-4个表(当然,用于生成SELECT的表列表可能与用于在WHERE中放置条件的表列表重叠)。

我可以编写一个VIEW,其中包含加入所有这100个表的底层代码。然后我可以将上面提到的SQL查询写到这个VIEW。但在这种情况下,对我来说如何指示SQL Server(尽管代码中的明确指令连接所有这100个表)只有大约11个表应该连接(11个表足以连接以生成SELECT)是一个很大的问题。结果并考虑到WHERE条件)。

另一种方法可能是创建一个转换以下“假”代码的“功能”

SELECT field1, field2, field3 FROM TheFakeTable WHERE field1=12 and field4=5

进入以下“真实”代码:

SELECT T1.field1, T2.field2, T3.field3 FROM TheRealMainTable 
join T1 on ....
join T2 on ....
join T3 on ....
join T4 on ....
WHERE T1.field1=12 and T4.field4=5

从语法的角度来看,即使允许这种“TheFakeTable-mechanism”与真实表格和结构的任何混合组合也不是问题。这里真正的问题是如何在技术上实现这个“特征”。我可以创建一个函数,它将“假”代码作为输入并生成“真实”代码。但它不方便,因为它需要使用动态SQL工具evrywhere出现这个“TheFakeTable-mechanism”。幻想土地解决方案是在我的Management Studio中扩展SQL语言的语法,以允许编写这样的假代码,然后在发送到服务器之前自动将此代码转换为真实代码。

我的问题是:

  1. 在上述VIEW中,是否可以指示SQl Server只能连接11个表而不是100个表来进行shomehow(或天才enouh)?
  2. 如果我决定创建这个“TheFakeTable-mechanism”功能,那么这个功能的技术实现的最佳形式是什么?
  3. 感谢大家的每一条评论!

    PS 具有100个表的结构来自我在这里问的以下问题: Normalizing an extremely big table

3 个答案:

答案 0 :(得分:16)

SQL Server优化器确实包含删除冗余连接的逻辑,但存在限制,连接必须为provably redundant。总而言之,连接可以有四种效果:

  1. 它可以添加额外的列(来自连接表)
  2. 它可以添加额外的行(连接的表可能多次匹配源行)
  3. 它可以删除行(连接的表可能没有匹配)
  4. 它可以引入NULL s(对于RIGHTFULL JOIN
  5. 要成功删除冗余连接,查询(或视图)必须考虑所有四种可能性。如果这样做,正确的话,效果可能会令人惊讶。例如:

    USE AdventureWorks2012;
    GO
    CREATE VIEW dbo.ComplexView
    AS
        SELECT
            pc.ProductCategoryID, pc.Name AS CatName,
            ps.ProductSubcategoryID, ps.Name AS SubCatName,
            p.ProductID, p.Name AS ProductName,
            p.Color, p.ListPrice, p.ReorderPoint,
            pm.Name AS ModelName, pm.ModifiedDate
        FROM Production.ProductCategory AS pc
        FULL JOIN Production.ProductSubcategory AS ps ON
            ps.ProductCategoryID = pc.ProductCategoryID
        FULL JOIN Production.Product AS p ON
            p.ProductSubcategoryID = ps.ProductSubcategoryID
        FULL JOIN Production.ProductModel AS pm ON
            pm.ProductModelID = p.ProductModelID
    

    优化器可以成功简化以下查询:

    SELECT
        c.ProductID,
        c.ProductName
    FROM dbo.ComplexView AS c
    WHERE
        c.ProductName LIKE N'G%';
    

    要:

    Simplified plan

    Rob Farley在original MVP Deep Dives book中深入探讨了这些想法,SQLBits上有recording of him presenting on the topic

    主要限制是外键关系must be based on a single key有助于简化过程,而针对此类视图的查询的编译时间可能会变得非常长,特别是当连接数增加时。编写一个100表视图可以让所有语义完全正确,这可能是一个很大的挑战。我倾向于找到一种替代解决方案,可能使用dynamic SQL

    也就是说,非规范化表的特殊性质可能意味着视图的组合非常简单,只需要强制FOREIGN KEYsNULL能够引用的列和适当的UNIQUE约束使这个解决方案按照你的意愿工作,而不需要计划中100个物理连接操作符的开销。

    实施例

    使用十个表而不是一百个:

    -- Referenced tables
    CREATE TABLE dbo.Ref01 (col01 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
    CREATE TABLE dbo.Ref02 (col02 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
    CREATE TABLE dbo.Ref03 (col03 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
    CREATE TABLE dbo.Ref04 (col04 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
    CREATE TABLE dbo.Ref05 (col05 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
    CREATE TABLE dbo.Ref06 (col06 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
    CREATE TABLE dbo.Ref07 (col07 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
    CREATE TABLE dbo.Ref08 (col08 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
    CREATE TABLE dbo.Ref09 (col09 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
    CREATE TABLE dbo.Ref10 (col10 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
    

    父表定义(带页面压缩):

    CREATE TABLE dbo.Normalized
    (
        pk      integer IDENTITY NOT NULL,
        col01   tinyint NOT NULL REFERENCES dbo.Ref01,
        col02   tinyint NOT NULL REFERENCES dbo.Ref02,
        col03   tinyint NOT NULL REFERENCES dbo.Ref03,
        col04   tinyint NOT NULL REFERENCES dbo.Ref04,
        col05   tinyint NOT NULL REFERENCES dbo.Ref05,
        col06   tinyint NOT NULL REFERENCES dbo.Ref06,
        col07   tinyint NOT NULL REFERENCES dbo.Ref07,
        col08   tinyint NOT NULL REFERENCES dbo.Ref08,
        col09   tinyint NOT NULL REFERENCES dbo.Ref09,
        col10   tinyint NOT NULL REFERENCES dbo.Ref10,
    
        CONSTRAINT PK_Normalized
            PRIMARY KEY CLUSTERED (pk)
            WITH (DATA_COMPRESSION = PAGE)
    );
    

    观点:

    CREATE VIEW dbo.Denormalized
    WITH SCHEMABINDING AS
    SELECT
        item01 = r01.item,
        item02 = r02.item,
        item03 = r03.item,
        item04 = r04.item,
        item05 = r05.item,
        item06 = r06.item,
        item07 = r07.item,
        item08 = r08.item,
        item09 = r09.item,
        item10 = r10.item
    FROM dbo.Normalized AS n
    JOIN dbo.Ref01 AS r01 ON r01.col01 = n.col01
    JOIN dbo.Ref02 AS r02 ON r02.col02 = n.col02
    JOIN dbo.Ref03 AS r03 ON r03.col03 = n.col03
    JOIN dbo.Ref04 AS r04 ON r04.col04 = n.col04
    JOIN dbo.Ref05 AS r05 ON r05.col05 = n.col05
    JOIN dbo.Ref06 AS r06 ON r06.col06 = n.col06
    JOIN dbo.Ref07 AS r07 ON r07.col07 = n.col07
    JOIN dbo.Ref08 AS r08 ON r08.col08 = n.col08
    JOIN dbo.Ref09 AS r09 ON r09.col09 = n.col09
    JOIN dbo.Ref10 AS r10 ON r10.col10 = n.col10;
    

    破解统计信息以使优化器认为表非常大:

    UPDATE STATISTICS dbo.Normalized WITH ROWCOUNT = 100000000, PAGECOUNT = 5000000;
    

    示例用户查询:

    SELECT
        d.item06,
        d.item07
    FROM dbo.Denormalized AS d
    WHERE
        d.item08 = 'Banana'
        AND d.item01 = 'Green';
    

    给我们这个执行计划:

    Execution plan 1

    规范化表的扫描看起来很糟糕,但是在存储引擎的扫描过程中会应用两个Bloom过滤器位图(因此无法匹配的行甚至不会显示到查询处理器)。这可能足以在您的情况下提供可接受的性能,当然比使用溢出的列扫描原始表更好。

    如果您能够在某个阶段升级到SQL Server 2012 Enterprise,则还有另一种选择:在规范化表上创建列存储索引:

    CREATE NONCLUSTERED COLUMNSTORE INDEX cs 
    ON dbo.Normalized (col01,col02,col03,col04,col05,col06,col07,col08,col09,col10);
    

    执行计划是:

    Columnstore Plan

    这可能看起来更糟糕,但列存储提供了出色的压缩,整个执行计划在批处理模式下运行,并为所有贡献列提供过滤器。如果服务器有足够的线程和可用内存,那么这种替代方案可能真的会飞。

    最终,考虑到表的数量以及获得糟糕的执行计划或需要过多编译时间的可能性,我不确定这种规范化是否正确。我可能会首先纠正非规范化表的模式(正确的数据类型等),可能应用数据压缩...通常的事情。

    如果数据真正属于星型模式,那么它可能需要更多的设计工作,而不仅仅是将重复的数据元素拆分成单独的表。

答案 1 :(得分:3)

为什么你认为加入100个表会是性能问题?

如果所有键都是主键,则所有连接都将使用索引。那么唯一的问题是索引是否适合内存。如果它们适合记忆,性能可能根本不是问题。

在进行此类声明之前,您应该尝试使用100个连接进行查询。

此外,根据原始问题,参考表中只有几个值。表本身适合单个页面,另外还有索引的另一个页面。这是200页,最多占用几兆字节的页面缓存。不要担心优化,创建视图,如果遇到性能问题,请考虑后续步骤。不要预先假定性能问题。

阐述:

这已收到很多评论。让我解释为什么这个想法可能不像听起来那么疯狂。

首先,我假设所有连接都是通过主键索引完成的,并且索引适合内存。

页面上的100个键占用400个字节。假设原始字符串平均每个字节为40个字节。这些将占用页面上的4,000个字节,因此我们可以节省成本。实际上,在前一个方案中,大约有2个记录适合页面。大约20个符合键的页面。

因此,使用键读取记录的在I / O 方面比读取原始记录快10倍。通过对少量值的假设,索引和原始数据适合内存。

阅读20条记录需要多长时间?旧方法需要阅读10页。使用键,有一个页面读取和100 * 20索引查找(可能需要额外的查找来获取值)。根据系统的不同,2,000个索引查找可能比额外的9个页面I / O更快 - 甚至更快。我想说的是这是一个合理的情况。它可能会或可能不会发生在特定系统上,但它并不疯狂。

这有点过于简单了。 SQL Server实际上并不是一次一页地读取页面。我认为它们是以4个为一组进行读取的(在进行全表扫描时可能会有先行读取)。但另一方面,在大多数情况下,表扫描查询将比处理器绑定更多I / O限制,因此在参考表中查找值时有备用处理器周期。

事实上,使用密钥可以导致更快读取表而不是使用它们,因为备用处理周期将用于查找(“备用”,因为处理能力是阅读时可用)。实际上,带有键的表可能足够小,可以放入可用的缓存中,从而大大提高了更复杂查询的性能。

实际性能取决于很多因素,例如字符串的长度,原始表(它是否大于可用缓存?),底层硬件执行I / O读取和处理的能力时间,以及依赖查询优化器正确进行连接。

我原来的观点是假设先验 100连接是坏事是不正确的。需要对该假设进行测试,使用密钥甚至可以提升性能。

答案 2 :(得分:0)

如果您的数据变化不大,您可能会受益于创建Indexed View,这基本上会实现视图。

如果数据经常更改,则可能不是一个好选项,因为服务器必须为视图的基础表中的每个更改维护索引视图。

这是一个很好的blog post,可以更好地描述它。

来自博客:

CREATE VIEW dbo.vw_SalesByProduct_Indexed
 WITH SCHEMABINDING
 AS
      SELECT 
            Product, 
            COUNT_BIG(*) AS ProductCount, 
            SUM(ISNULL(SalePrice,0)) AS TotalSales
      FROM dbo.SalesHistory
      GROUP BY Product
 GO
  

下面的脚本在我们的视图上创建索引:

CREATE UNIQUE CLUSTERED INDEX idx_SalesView ON vw_SalesByProduct_Indexed(Product)
  

显示已在视图上创建索引并且确实已创建索引   占用数据库中的空间,运行以下脚本来查找   聚集索引中有多少行以及视图有多少空间   接受了。

EXECUTE sp_spaceused 'vw_SalesByProduct_Indexed'
  

下面的SELECT语句与之前的语句相同,除外   这次它执行聚簇索引搜索,这通常是非常的   快。

SELECT 
      Product, TotalSales, ProductCount 
 FROM vw_SalesByProduct_Indexed
 WHERE Product = 'Computer'