SQL Server存储过程的性能问题

时间:2009-09-18 13:03:13

标签: c# sql sql-server stored-procedures performance

我使用ANTS探查器来识别我的C#应用​​程序中的剩余瓶颈:SQL Server存储过程。我正在使用SQL Server 2008.这里的任何人都可以帮助我提高性能,或者指出我可以做些什么来使其更好或更高效?

首先,这是程序:

PROCEDURE [dbo].[readerSimilarity] 
-- Add the parameters for the stored procedure here
@id int,
@type int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;

-- Insert statements for procedure here
IF (@type=1) --by Article
    SELECT id1, id2, similarity_byArticle FROM similarity WHERE (id1 = @id OR id2 = @id) 
AND similarity_byArticle != 0

ELSE IF (@type=2) --by Parent
    SELECT id1, id2, similarity_byParent FROM similarity WHERE (id1 = @id OR id2 = @id) 
AND similarity_byParent != 0

ELSE IF (@type=3) --by Child
    SELECT id1, id2, similarity_byChild FROM similarity WHERE (id1 = @id OR id2 = @id) 
AND similarity_byChild != 0

ELSE IF (@type=4) --combined
    SELECT id1, id2, similarity_combined FROM similarity WHERE (id1 = @id OR id2 = @id) 
AND similarity_combined != 0

END

表格“similarity”由两个id s(id1id2)以及一些存储double值的列组成。约束是id1 < id2

Column     Data
-----      ----
ID1         PK, Indexed
ID2         PK, Indexed

The table contains 28.5 million entries.

存储过程背景

存储过程的工作是获取idid1中具有参数id2的所有行。此外,type-parameter指定的列不能为零。

对于不同的ids,多次调用存储过程。虽然每次呼叫只接受~1.6 ms,但总共召唤了17,000次。

处理器的运行率仅为25%,这似乎是因为应用程序正在等待程序调用返回。

你认为有什么方法可以加快速度吗?

调用存储过程C# Code Snippet

private HashSet<NodeClustering> AddNeighbourNodes(int id)
    {
        HashSet<NodeClustering> resultSet = new HashSet<NodeClustering>();
        HashSet<nodeConnection> simSet = _graphDataLoader.LoadEdgesOfNode(id);

        foreach (nodeConnection s in simSet)
        {
            int connectedId = s.id1;
            if (connectedId == id)
                connectedId = s.id2;

            // if the corresponding node doesn't exist yet, add it to the graph
            if (!_setNodes.ContainsKey(connectedId))
            {
                NodeClustering nodeToAdd = CreateNode(connectedId);
                GraphAddOuter(nodeToAdd);
                ChangeWeightIntoCluster(nodeToAdd.id, s.weight);
                _bFlowOuter += s.weight;
                resultSet.Add(nodeToAdd);
            }
        }

        // the nodes in the result set have been added 
                   to the outernodes -> add to the outernodes count
        _setNodes[id].countEdges2Outside += resultSet.Count;

        return resultSet;
    }

C#代码背景信息

每次向集群添加新id时都会调用此方法。它获取id的所有连接节点(当数据库中有id1=idid2=id的条目时,它们已连接)

_graphDataLoader.LoadEdgesOfNode(id);

然后它检查所有连接的ids以及它们是否尚未加载:

if (!_setNodes.ContainsKey(connectedId))

它加载它们:

CreateNode(connectedId); 

方法:

_graphDataLoader.LoadEdgesOfNode(id); 
再次调用

,这次使用connectedId

我需要这个来获取新节点与已经在集合中的那些节点的所有连接。

我可能会收集我需要添加的所有节点的ids,并仅使用一个id列表调用我的存储过程一次。

我可以通过像

这样的东西一次加载连接的ids连接
        SELECT id1, id2, similarity_byArticle FROM similarity WHERE 
                   (id1 = @id OR id2 = @id OR
        id1 IN (SELECT id1 FROM similarity WHERE id2 = @id) OR 
        id2 IN (SELECT id1 FROM similarity WHERE id2 = @id) OR
        id1 IN (SELECT id2 FROM similarity WHERE id1 = @id) OR 
        id2 IN (SELECT id2 FROM similarity WHERE id1 = @id))
                    AND similarity_byArticle != 0

然后我会得到比我需要的更多的条目,因为我也会为已经加载的节点获取它们(从我的测试中,它将占调用的大约75%)。

问题

  1. 如何加快存储过程?
  2. 我能以不同的方式做,有更高效的方式吗?
  3. 我可以使用List<int>作为SP参数吗?
  4. 还有其他想法吗?

4 个答案:

答案 0 :(得分:6)

如果它运行得那么快,那么问题可能在于重复调用该程序的次数。有没有办法可以修改存储过程和代码,以便在一次调用中返回应用程序所需的所有结果?

优化在不到2毫秒内运行的查询可能不是一项富有成效的工作。我怀疑你可以通过查询调整来削减超过几分之一毫秒。

答案 1 :(得分:3)

我尝试将应用程序更改为仅对每个ID调用一次,但如果不可能,请尝试这样做(确保在interact.id1上有一个索引,在similar.id2上有另一个索引):

PROCEDURE [dbo].[readerSimilarity] 
-- Add the parameters for the stored procedure here
@id int,
@type int
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    -- Insert statements for procedure here
    IF @type=1 --by Article
    BEGIN
        SELECT
            id1, id2,similarity_byArticle
            FROM similarity
            WHERE id1 = @id AND similarity_byArticle!=0
        UNION
        SELECT
            id1, id2,similarity_byArticle
            FROM similarity
            WHERE id2 = @id AND similarity_byArticle!=0

    END
    ELSE IF @type=2 --by Parent
    BEGIN
        SELECT
            id1, id2,similarity_byParent
            FROM similarity
            WHERE id1 = @id AND similarity_byParent!=0
        UNION
        SELECT
            id1, id2,similarity_byParent
            FROM similarity
            WHERE id2 = @id AND similarity_byParent!=0

    END

    ELSE IF @type=3 --by Child
    BEGIN
        SELECT
            id1, id2,similarity_byChild
            FROM similarity
            WHERE id1 = @id AND similarity_byChild!=0
        UNION
        SELECT
            id1, id2,similarity_byChild
            FROM similarity
            WHERE id2 = @id AND similarity_byChild!=0

    END
    ELSE IF @type=4 --combined
    BEGIN
        SELECT
            id1, id2,similarity_combined
            FROM similarity
            WHERE id1 = @id AND similarity_combined!=0
        UNION
        SELECT
            id1, id2,similarity_combined
            FROM similarity
            WHERE id2 = @id AND similarity_combined!=0

    END

END

GO

编辑基于OP的最新评论:

  

整个图表存储在   MSSQL-Database和我加载它   先后将程序纳入   一些字典结构

您需要重新设计加载过程。您应该只调用一次数据库来加载所有这些数据。由于ID已存在于Database表中,因此您可以在此查询中使用连接从另一个表中获取正确的ID。使用包含要绘制图表的ID的表架构编辑您的问题,以及它们与已发布代码的关系。一旦您获得单个查询以返回所有数据,每次17,000次调用单个行的速度会快得多。

答案 2 :(得分:1)

使用分隔列表(使用逗号或斜杠或其他任何东西,使用管道符[[]],立即将所有ID传递到存储过程中。 将下面列出的用户定义函数(UDF)添加到数据库中。它会将分隔列表转换为可以加入相似表的表。然后在你的实际存储过程中,你可以写...

Create Procedure GetSimilarityIDs
@IdValues Text -- @IdValues is pipe-delimited [|] list of Id Values
As
Set NoCount On
Declare @IDs Table 
   (rowNum Integer Primary Key Identity Not Null,
    Id Integer Not Null)
Insert Into @IDs(Id)
Select Cast(sVal As Integer)
From dbo.ParseString(@IdValues, '|') -- specify delimiter
-- ---------------------------------------------------------

Select id1, id2, similarity_byArticle            
From similarity s Join @IDs i On i.Id = s.Id
Where similarity_byArticle <> 0
Return 0

- ********************************************* **************

下面的代码是创建泛型函数UDF,它可以将任何文本字符串解析为字符串值表...:

Create FUNCTION [dbo].[ParseTextString] (@S Text, @delim VarChar(5))
Returns @tOut Table 
    (ValNum Integer Identity Primary Key, 
     sVal VarChar(8000))
As
Begin 
Declare @dLLen TinyInt       -- Length of delimiter
Declare @sWin  VarChar(8000) -- Will Contain Window into text string
Declare @wLen  Integer       -- Length of Window
Declare @wLast TinyInt     -- Boolean to indicate processing Last Window
Declare @wPos  Integer     -- Start Position of Window within Text String
Declare @sVal  VarChar(8000) -- String Data to insert into output Table
Declare @BtchSiz Integer     -- Maximum Size of Window
    Set @BtchSiz = 7900      -- (Reset to smaller values to test routine)
Declare @dPos Integer        -- Position within Window of next Delimiter
Declare @Strt Integer        -- Start Position of each data value within Window
-- -------------------------------------------------------------------------
If @delim is Null Set @delim = '|'
If DataLength(@S) = 0 Or
      Substring(@S, 1, @BtchSiz) = @delim Return
-- ---------------------------
Select @dLLen = Len(@delim),
       @Strt = 1, @wPos = 1,
       @sWin = Substring(@S, 1, @BtchSiz)
Select @wLen = Len(@sWin),
       @wLast = Case When Len(@sWin) = @BtchSiz
           Then 0 Else 1 End,
       @dPos = CharIndex(@delim, @sWin, @Strt)
-- ------------------------------------
  While @Strt <= @wLen
  Begin
      If @dPos = 0 -- No More delimiters in window
      Begin                      
          If @wLast = 1 Set @dPos = @wLen + 1 
          Else 
          Begin
              Set @wPos = @wPos + @Strt - 1
              Set @sWin = Substring(@S, @wPos, @BtchSiz)
              -- ----------------------------------------
              Select @wLen = Len(@sWin), @Strt = 1,
                     @wLast = Case When Len(@sWin) = @BtchSiz
                              Then 0 Else 1 End,
                     @dPos = CharIndex(@delim, @sWin, 1)
              If @dPos = 0 Set @dPos = @wLen + 1 
          End
      End
      -- -------------------------------
      Set @sVal = LTrim(Substring(@sWin, @Strt, @dPos - @Strt))
      Insert @tOut (sVal) Values (@sVal)
      -- -------------------------------
      -- Move @Strt to char after last delimiter
      Set @Strt = @dPos + @dLLen 
      Set @dPos = CharIndex(@delim, @sWin, @Strt)
   End
   Return
End

答案 3 :(得分:0)

首先创建一个视图

CREATE VIEW ViewArticles
AS
SELECT id1, id2, similarity_byArticle 
FROM similarity 
WHERE (id1 = @id or id2 = @id) 
and similarity_byArticle != 0

在您的代码中将所有需要的ID填充到表中。

创建一个以所有id表作为参数的函数。

CREATE FUNCTION
  SelectArticles
(
  @Ids TABLE
)
RETURNS TABLE
AS
RETURN
(
     SELECT id1, id2, similarity_byArticle FROM ViewArticles
     INNER JOIN @Ids I ON I.Id = id1
     UNION
     SELECT id1, id2, similarity_byArticle FROM ViewArticles
     INNER JOIN @Ids I ON I.Id = id2
)