Проблема производительности с хранимой процедурой SQL Server - PullRequest
1 голос
/ 18 сентября 2009

Я использовал профилировщик 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 (id1 и id2) и ряда столбцов, в которых хранятся значения double. Ограничением является то, что id1 < id2.

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

The table contains 28.5 million entries.

Фон хранимой процедуры

Задача хранимой процедуры - получить все строки, имеющие параметр id, в id1 или id2. Кроме того, столбец, указанный в параметре типа, не может быть нулем.

Хранимая процедура вызывается несколько раз для разных ids. Несмотря на то, что он принимает ~1.6 ms за звонок, он суммирует, когда звонит 17000 раз.

Процессор работает только на 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=id или id2=id) через

_graphDataLoader.LoadEdgesOfNode(id);

Затем проверяются все подключенные ids и, если они еще не загружены:

if (!_setNodes.ContainsKey(connectedId))

Это загружает их:

CreateNode(connectedId); 

Метод:

_graphDataLoader.LoadEdgesOfNode(id); 

вызывается снова, на этот раз с connectedId.

Мне нужно это, чтобы получить все соединения новых узлов с теми узлами, которые уже находятся в наборе.

Возможно, я смогу собрать 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 ]

6 голосов
/ 18 сентября 2009

Если он запускается так быстро, ваша проблема, вероятно, заключается в большом количестве повторных обращений к процедуре. Есть ли способ, которым вы можете изменить хранимую процедуру и код, чтобы вернуть все результаты, необходимые приложению, за один вызов?

Оптимизация запроса, который выполняется менее чем за 2 мс, вероятно, не является плодотворным усилием. Я сомневаюсь, что вы сможете сэкономить больше, чем доли миллисекунды с настройками запросов.

3 голосов
/ 18 сентября 2009

Я бы попробовал изменить приложение так, чтобы оно вызывалось только один раз для каждого идентификатора, но если это невозможно, попробуйте это (убедитесь, что есть индекс для Similarity.id1 и другой индекс для Similarity.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

РЕДАКТИРОВАТЬ на основании последнего комментария ОП:

Весь график хранится в MSSQL-база данных и я ее загружаю последовательно с процедурой в некоторые словарные структуры

Вам необходимо изменить процесс загрузки. Вы должны вызвать базу данных только один раз, чтобы загрузить все эти данные. Поскольку идентификаторы уже находятся в таблице базы данных, вы можете использовать соединение в этом запросе, чтобы получить правильные идентификаторы из другой таблицы. отредактируйте свой вопрос с помощью схемы таблицы, содержащей идентификаторы на графике и их связь с уже опубликованным кодом. Как только вы получите один запрос для возврата всех данных, будет намного быстрее, чем 17 000 вызовов для одной строки каждый раз.

1 голос
/ 18 сентября 2009

Передайте все идентификаторы в хранимый процесс за раз, используя список с разделителями (используйте запятую или косую черту или что-то еще, я использую символ трубы [|] .. Добавьте пользовательскую функцию (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
0 голосов
/ 18 сентября 2009

Сначала создайте вид

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

В вашем коде введите все необходимые идентификаторы в таблицу.

Создать функцию, которая принимает в качестве параметра все таблицы идентификаторов.

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
)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...