【问题标题】:Optimizing CLUSTERED INDEX for use with JOIN优化 CLUSTERED INDEX 以与 JOIN 一起使用
【发布时间】:2011-10-25 17:42:53
【问题描述】:

optin_channel_1(每个“频道”都有一个专用表)

CREATE TABLE [dbo].[optin_channel_1](
    [key_id] [bigint] NOT NULL,
    [valid_to] [datetime] NOT NULL,
    [valid_from] [datetime] NOT NULL,
    [key_type_id] [int] NOT NULL,
    [optin_flag] [tinyint] NOT NULL,
    [source_proc_id] [int] NOT NULL,
    [date_inserted] [datetime] NOT NULL
) ON [PRIMARY]

CREATE CLUSTERED INDEX [ix_id] ON [dbo].[optin_channel_1] 
(
    [key_type_id] ASC,
    [key_id] ASC,
    [valid_to] ASC,
    [valid_from] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

profile_conns

CREATE TABLE [dbo].[profile_conns](
    [profile_key_id] [bigint] NOT NULL,
    [valid_to] [datetime] NOT NULL,
    [valid_from] [datetime] NOT NULL,
    [conn_key_id] [bigint] NOT NULL,
    [conn_key_type_id] [int] NOT NULL,
    [conn_type_id] [int] NOT NULL,
    [source_proc_id] [int] NOT NULL,
    [date_inserted] [datetime] NOT NULL
) ON [PRIMARY]

CREATE CLUSTERED INDEX [ix_id] ON [dbo].[profile_conns] 
(
    [profile_key_id] ASC,
    [conn_key_type_id] ASC,
    [conn_key_id] ASC,
    [valid_to] ASC,
    [valid_from] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

lu_channel_conns

CREATE TABLE [dbo].[lu_channel_conns](
    [channel_id] [int] NOT NULL,
    [conn_type_id] [int] NOT NULL,
 CONSTRAINT [PK_lu_channel_conns] PRIMARY KEY CLUSTERED 
(
    [channel_id] ASC,
    [conn_type_id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

lu_conn_type

CREATE TABLE [dbo].[lu_conn_type](
    [conn_type_id] [int] NOT NULL,
    [default_key_type_id] [int] NOT NULL,
    [master_key_type_id] [int] NOT NULL,
    [date_inserted] [datetime] NOT NULL,
 CONSTRAINT [PK_lu_conns] PRIMARY KEY CLUSTERED 
(
    [conn_type_id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

查看v_source_proc_id_by_group_id

SELECT DISTINCT x.source_proc_id, x.source_proc_group_id
FROM lu_source_proc x INNER JOIN lu_source_proc_group y ON x.source_proc_group_id = y.group_id

有一条动态 SQL 语句要执行:

SET @sql_str='SELECT @ret=MAX(o.optin_flag)
    FROM optin_channel_'+CAST(@channel_id AS NVARCHAR(100))+' o
    INNER HASH JOIN dbo.v_source_proc_id_by_group_id y ON o.source_proc_id=y.source_proc_id AND y.source_proc_group_id=@source_proc_group_id
    INNER HASH JOIN profile_conns z ON z.profile_key_id=cast(@profile_key_id AS NVARCHAR(100)) AND z.conn_key_type_id=o.key_type_id AND z.conn_key_id=o.[key_id] AND z.valid_to=''01.01.3000''
    INNER HASH JOIN lu_channel_conns x ON x.channel_id=@channel_id AND z.conn_type_id=x.conn_type_id
    INNER HASH JOIN lu_conn_type ct ON ct.conn_type_id=x.conn_type_id AND ct.default_key_type_id=o.key_type_id'
SET @param='@channel_id INT, @profile_key_id INT, @source_proc_group_id INT, @ret NVARCHAR(400) OUTPUT'
EXEC sp_executesql @sql_str,@param,@channel_id,@profile_key_id,@source_proc_group_id,@ret OUTPUT

即这给出了:

SELECT @ret=MAX(o.optin_flag) AS optin_flag
FROM optin_channel_1 o
INNER HASH JOIN dbo.v_source_proc_id_by_group_id y 
    ON o.source_proc_id=y.source_proc_id 
    AND y.source_proc_group_id=5
INNER HASH JOIN profile_conns z 
    ON z.profile_key_id=1 
    AND z.conn_key_type_id=o.key_type_id 
    AND z.conn_key_id=o.[key_id] 
    AND z.valid_to='01.01.3000'
INNER HASH JOIN lu_channel_conns x 
    ON x.channel_id=1 
    AND z.conn_type_id=x.conn_type_id
INNER HASH JOIN lu_conn_type ct 
    ON ct.conn_type_id=x.conn_type_id 
    AND ct.default_key_type_id=o.key_type_id

这些表用于 optin 数据库。 optin_flag 可以是 0 或 1。在最后一条语句中,当 optin 通过属于source_proc_group_id=5。我希望这足以理解发生了什么。

这是使用CLUSTERED INDEX'es 的最佳方式吗?还是从profile_conns 的索引中删除profile_key_id 并将z.profile_key_id=1 放在WHERE 子句中会更好吗?

可能有更好的方法来优化此选择(数据库架构无法更改,只能更改索引和修改语句)。

【问题讨论】:

  • 您当前的方法是否存在性能问题,或者您认为您会遇到潜在的性能问题吗?
  • 我们只是计划在未来做一些密集的测试。这个动态 SQL 只是一个表上一个巨大的触发器的一部分,当触发器运行时,这部分消耗了超过 50% 的完整时间,因为这条语句为 30 个通道运行了 30 次。我预计触发器的运行时间为 200-300 毫秒。但现在它运行大约一秒钟。触发器的其余部分肯定你不知道,但是上面的SQL占用的时间最多,所以我觉得应该优化一下。

标签: sql sql-server-2005 tsql indexing clustered-index


【解决方案1】:

如果不知道表的大小和存储在其中的数据类型,就很难衡量。

假设 optin_channel_1 有很多数据,而 profile_cons 有很多数据,我会尝试以下方法:

  • optin_channel_1(key_id) 或 key_type_id 上的聚集索引取决于哪个字段具有最不同的值。 (因为您没有覆盖索引)
  • profile_conns (cons_key_id) 或 cons_key_type_id 上的聚集索引,具体取决于您在 optin_channel_1 中选择的内容
  • 等等……

基本上,如果您的表 profile_conns 表没有太多数据,我会将聚集索引放在最碎片化的“过滤”字段上(我怀疑 profile_key_id)。如果表有很多数据,我会瞄准散列/合并连接并将聚集索引与 optin_channel_1 表的聚集索引匹配。

我也会这样重写查询:

SELECT @ret = MAX(o.optin_flag) AS optin_flag
  FROM optin_channel_1 o
  JOIN dbo.v_source_proc_id_by_group_id y 
    ON o.source_proc_id = y.source_proc_id  
  JOIN profile_conns z 
    ON z.conn_key_type_id = o.key_type_id 
   AND z.conn_key_id = o.[key_id] 
  JOIN lu_channel_conns x 
    ON z.conn_type_id = x.conn_type_id
  JOIN lu_conn_type ct 
    ON ct.conn_type_id = x.conn_type_id 
   AND ct.default_key_type_id=o.key_type_id 
 WHERE y.source_proc_group_id = 5
   AND z.profile_key_id = 1 
   AND x.channel_id = 1 
   AND z.valid_to = '01.01.3000'

查询以这种方式更改是因为:

  • 将过滤条件放在 where 子句中会向您展示哪些相关字段是针对哈希/合并连接的
  • 放置连接提示很少是一个好主意。很难击败查询调控器来确定最佳查询计划。糟糕的计划通常表明您的索引/统计数据存在问题。

总结如下:

  • 小表连接到大表 ==> 进行嵌套循环并将聚集索引集中在小表中的“过滤器”字段和大表中的连接字段上。
  • big table join to big table => 进行 hash/merge join 并将聚集索引放在两边的匹配字段上
  • 多字段索引通常只有在“覆盖”时才是一个好主意,这意味着您查询的所有字段都包含在索引中。 (或包含在 include() 子句中)

【讨论】:

  • +1 用于删除JOIN 提示并将过滤器移动到WHERE 子句。
  • 我的经验是,将条件放在联接中有时会带来更好的性能。在我开发的一个数据库中,有一个多值连接表(例如电子邮件上的抄送列表),记录超过 1 亿条,主表大约有 100 万条记录。如果连接上的查询条件将 1 亿条记录表过滤到 100 条记录,那么在连接之前执行过滤器会产生更好的性能。如果条件在连接中并且我无法通过表提示获得这种效果,那么 SQL 最好在连接之前执行该过滤器。
  • @BalamBalam 这听起来像是导致错误查询计划的不正确统计信息。据我所知,查询调控器制定了完全相同的查询计划。这个周末我会做一些测试来仔细检查。
  • 好的,谢谢你的时间,我下周去测试。没错,大桌子是optin_channel_1profile_conns(几乎相同大小)。正如您提到的,聚集索引应该在 key_type_idkey_id 上,因为这些列属于连接。我的想法是将profile_key_id 也放入profile_conns 的聚集索引中,因为此表上还有很多SELECTs,它们在此字段上过滤WHERE
  • only if the selects also filter on the key_id and key_type_id it will have any use to put the profile_key_id in.
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-04-01
  • 2012-04-02
  • 1970-01-01
  • 2013-04-09
  • 2015-02-15
  • 2012-03-28
  • 1970-01-01
相关资源
最近更新 更多