SQL 使用表索引优化服务器查询
SQL Server query optimization using indexes on tables
我在 SQL 服务器函数中有这个查询,它需要大约 3 秒来执行单个值。当记录数较多时,函数调用时间累加。
我正在展示表的结构和现有的索引。只是检查建议是否有任何我可以添加的索引以获得更好的查询性能
例如 - 对于 Table B 在 PId
列上与 Table U 连接。
当前索引 [IX_TableB_2]
在 PRId
上,它在 WHERE
子句中用于 b.PRID <> @PRID
.
的条件
对于这个特定查询,这些表是否有更好的索引建议?
谢谢
--Query
SELECT
ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC), b.BId, COUNT(*)
FROM
TableB b
INNER JOIN
TableU u ON u.PID = b.PID
LEFT JOIN
TableP p ON p.BName = b.BName
WHERE
LEFT(b.Code, @CheckLen) = LEFT(@Code, @CheckLen)
AND b.PRID <> @PRID
AND ISNULL(p.DSID, @DSID) = @DSID
GROUP BY
b.BId
Table 行数和结构
- TableB:100 万行
--Existing Indexes on the TableB
CREATE CLUSTERED INDEX [IX_TableB_1]
ON [TableB] ([Code] ASC)
CREATE NONCLUSTERED INDEX [IX_TableB_2]
ON [TableB] ([PRID] ASC)
INCLUDE([Code], [BId], [BName], [PID])
- TableU:300 万行
--Existing Indexes on TableU
ALTER TABLE TableU
ADD CONSTRAINT [PK_TableU]
PRIMARY KEY CLUSTERED ([PID] ASC)
- TableP:600 行
--Existing Indexes on TableP
ALTER TABLE TableP
ADD CONSTRAINT [PK_TableP]
PRIMARY KEY CLUSTERED (BName ASC, DSID ASC)
您说 LEFT(b.Code, @CheckLen)
上的筛选器比筛选器 <> @PRID
对来自 TableB
的行的限制更多。所以最好只依赖 Code
上的聚簇索引。要命中索引,您需要更改 WHERE
子句
b.Code LIKE LEFT(@Code, @CheckLen) + '%'
此外,TableP
上的 left-join 然后在 WHERE
中使用 ISNULL
过滤。鉴于 DSID
不可为空,逻辑可以翻转为 NOT EXISTS
。该索引目前是正确的:首先是 BName
上的相等性,然后是 DSID
.
上的不等式
NOT EXISTS (SELECT 1
FROM TableP p
WHERE p.BName = b.BName
AND p.DSID <> @DSID
)
TableU
上的查找已经在使用聚集索引,没有其他列,因此无法在那里执行任何操作。
BId
上的分组在这里无能为力。因为 Code
上的过滤器是一个范围查找,所以不能使用索引键中后面的任何列。但这应该没什么大不了的,因为在所有连接之后分组之前你似乎只有 13 行,而且只有一个 BId
值,所以性能应该没问题。
ROW_NUMBER
会给您带来麻烦,因为它需要对 COUNT(*)
进行额外的排序。如果有任何方法可以删除它,我建议您这样做。
把这些放在一起
SELECT
ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC), b.BId, COUNT(*)
FROM
TableB b
INNER JOIN
TableU u ON u.PID = b.PID
WHERE
b.Code LIKE LEFT(@Code, @CheckLen) + '%'
AND b.PRID <> @PRID
AND NOT EXISTS (SELECT 1
FROM TableP p
WHERE p.BName = b.BName
AND p.DSID <> @DSID
)
GROUP BY
b.BId;
虽然您现在可以删除 TableB
上的 non-clustered 索引,但您无需更改索引。
或者,可能是 left-join 的逻辑一开始就错了,你真正需要的只是将条件移动到 ON
SELECT
ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC), b.BId, COUNT(*)
FROM
TableB b
INNER JOIN
TableU u ON u.PID = b.PID
LEFT JOIN
TableP p ON p.BName = b.BName
AND p.DSID = @DSID
WHERE
b.Code LIKE LEFT(@Code, @CheckLen) + '%'
AND b.PRID <> @PRID
GROUP BY
b.BId;
请注意,此版本有不同的结果:它不会排除其他表中的任何行。
索引应该相同。
我在 SQL 服务器函数中有这个查询,它需要大约 3 秒来执行单个值。当记录数较多时,函数调用时间累加。
我正在展示表的结构和现有的索引。只是检查建议是否有任何我可以添加的索引以获得更好的查询性能
例如 - 对于 Table B 在 PId
列上与 Table U 连接。
当前索引 [IX_TableB_2]
在 PRId
上,它在 WHERE
子句中用于 b.PRID <> @PRID
.
对于这个特定查询,这些表是否有更好的索引建议?
谢谢
--Query
SELECT
ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC), b.BId, COUNT(*)
FROM
TableB b
INNER JOIN
TableU u ON u.PID = b.PID
LEFT JOIN
TableP p ON p.BName = b.BName
WHERE
LEFT(b.Code, @CheckLen) = LEFT(@Code, @CheckLen)
AND b.PRID <> @PRID
AND ISNULL(p.DSID, @DSID) = @DSID
GROUP BY
b.BId
Table 行数和结构
- TableB:100 万行
--Existing Indexes on the TableB
CREATE CLUSTERED INDEX [IX_TableB_1]
ON [TableB] ([Code] ASC)
CREATE NONCLUSTERED INDEX [IX_TableB_2]
ON [TableB] ([PRID] ASC)
INCLUDE([Code], [BId], [BName], [PID])
- TableU:300 万行
--Existing Indexes on TableU
ALTER TABLE TableU
ADD CONSTRAINT [PK_TableU]
PRIMARY KEY CLUSTERED ([PID] ASC)
- TableP:600 行
--Existing Indexes on TableP
ALTER TABLE TableP
ADD CONSTRAINT [PK_TableP]
PRIMARY KEY CLUSTERED (BName ASC, DSID ASC)
您说 LEFT(b.Code, @CheckLen)
上的筛选器比筛选器 <> @PRID
对来自 TableB
的行的限制更多。所以最好只依赖 Code
上的聚簇索引。要命中索引,您需要更改 WHERE
子句
b.Code LIKE LEFT(@Code, @CheckLen) + '%'
此外,TableP
上的 left-join 然后在 WHERE
中使用 ISNULL
过滤。鉴于 DSID
不可为空,逻辑可以翻转为 NOT EXISTS
。该索引目前是正确的:首先是 BName
上的相等性,然后是 DSID
.
NOT EXISTS (SELECT 1
FROM TableP p
WHERE p.BName = b.BName
AND p.DSID <> @DSID
)
TableU
上的查找已经在使用聚集索引,没有其他列,因此无法在那里执行任何操作。
BId
上的分组在这里无能为力。因为 Code
上的过滤器是一个范围查找,所以不能使用索引键中后面的任何列。但这应该没什么大不了的,因为在所有连接之后分组之前你似乎只有 13 行,而且只有一个 BId
值,所以性能应该没问题。
ROW_NUMBER
会给您带来麻烦,因为它需要对 COUNT(*)
进行额外的排序。如果有任何方法可以删除它,我建议您这样做。
把这些放在一起
SELECT
ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC), b.BId, COUNT(*)
FROM
TableB b
INNER JOIN
TableU u ON u.PID = b.PID
WHERE
b.Code LIKE LEFT(@Code, @CheckLen) + '%'
AND b.PRID <> @PRID
AND NOT EXISTS (SELECT 1
FROM TableP p
WHERE p.BName = b.BName
AND p.DSID <> @DSID
)
GROUP BY
b.BId;
虽然您现在可以删除 TableB
上的 non-clustered 索引,但您无需更改索引。
或者,可能是 left-join 的逻辑一开始就错了,你真正需要的只是将条件移动到 ON
SELECT
ROW_NUMBER() OVER (ORDER BY COUNT(*) DESC), b.BId, COUNT(*)
FROM
TableB b
INNER JOIN
TableU u ON u.PID = b.PID
LEFT JOIN
TableP p ON p.BName = b.BName
AND p.DSID = @DSID
WHERE
b.Code LIKE LEFT(@Code, @CheckLen) + '%'
AND b.PRID <> @PRID
GROUP BY
b.BId;
请注意,此版本有不同的结果:它不会排除其他表中的任何行。
索引应该相同。