Advantage Database Server: низкая производительность хранимых процедур - PullRequest
6 голосов
/ 30 марта 2010

У меня есть вопрос о выполнении хранимых процедур в ADS. Я создал простую базу данных со следующей структурой:

CREATE TABLE MainTable
(
   Id    INTEGER PRIMARY KEY,
   Name  VARCHAR(50),
   Value INTEGER
);

CREATE UNIQUE INDEX MainTableName_UIX ON MainTable ( Name );

CREATE TABLE SubTable
(
  Id     INTEGER PRIMARY KEY,
  MainId INTEGER, 
  Name   VARCHAR(50),
  Value  INTEGER
);

CREATE INDEX SubTableMainId_UIX ON SubTable ( MainId );
CREATE UNIQUE INDEX SubTableName_UIX ON SubTable ( Name );

CREATE PROCEDURE CreateItems
( 
  MainName  VARCHAR ( 20 ),
  SubName   VARCHAR ( 20 ),
  MainValue INTEGER,
  SubValue  INTEGER,
  MainId    INTEGER OUTPUT,
  SubId     INTEGER OUTPUT
) 
BEGIN 
  DECLARE @MainName  VARCHAR ( 20 ); 
  DECLARE @SubName   VARCHAR ( 20 );
  DECLARE @MainValue INTEGER; 
  DECLARE @SubValue  INTEGER;

  DECLARE @MainId    INTEGER;
  DECLARE @SubId     INTEGER;

  @MainName  = (SELECT MainName  FROM __input);
  @SubName   = (SELECT SubName   FROM __input);
  @MainValue = (SELECT MainValue FROM __input);
  @SubValue  = (SELECT SubValue  FROM __input);

  @MainId = (SELECT MAX(Id)+1 FROM MainTable);
  @SubId  = (SELECT MAX(Id)+1 FROM SubTable );

  INSERT INTO MainTable (Id, Name, Value) VALUES (@MainId, @MainName, @MainValue);
  INSERT INTO SubTable (Id, Name, MainId, Value) VALUES (@SubId, @SubName, @MainId, @SubValue);

  INSERT INTO __output SELECT @MainId, @SubId FROM system.iota;
END;

CREATE PROCEDURE UpdateItems
( 
  MainName  VARCHAR ( 20 ),
  MainValue INTEGER,
  SubValue  INTEGER
) 
BEGIN 
  DECLARE @MainName  VARCHAR ( 20 ); 
  DECLARE @MainValue INTEGER; 
  DECLARE @SubValue  INTEGER;

  DECLARE @MainId    INTEGER;

  @MainName  = (SELECT MainName  FROM __input);
  @MainValue = (SELECT MainValue FROM __input);
  @SubValue  = (SELECT SubValue  FROM __input);

  @MainId    = (SELECT TOP 1 Id  FROM MainTable WHERE Name = @MainName);

  UPDATE MainTable SET Value = @MainValue WHERE Id     = @MainId;
  UPDATE SubTable  SET Value = @SubValue  WHERE MainId = @MainId;
END;

CREATE PROCEDURE SelectItems
( 
  MainName        VARCHAR ( 20 ),
  CalculatedValue INTEGER OUTPUT
) 
BEGIN 
  DECLARE @MainName VARCHAR ( 20 ); 

  @MainName = (SELECT MainName FROM __input);

  INSERT INTO __output SELECT m.Value * s.Value FROM MainTable m INNER JOIN SubTable s ON m.Id = s.MainId WHERE m.Name = @MainName;
END;

CREATE PROCEDURE DeleteItems
( 
  MainName VARCHAR ( 20 )
) 
BEGIN 
  DECLARE @MainName VARCHAR ( 20 ); 
  DECLARE @MainId   INTEGER; 

  @MainName = (SELECT MainName FROM __input);
  @MainId   = (SELECT TOP 1 Id FROM MainTable WHERE Name = @MainName);

  DELETE FROM SubTable  WHERE MainId = @MainId;
  DELETE FROM MainTable WHERE Id     = @MainId;
END;

На самом деле проблема, с которой я столкнулся, - несмотря на то, что легкие хранимые процедуры работают очень-очень медленно (около 50-150 мс) относительно простых запросов (0-5 мс). Чтобы проверить производительность, я создал простой тест (в F # с использованием ADS ADO.NET провайдера):

open System;
open System.Data;
open System.Diagnostics;
open Advantage.Data.Provider;


let mainName = "main name #";
let subName  = "sub name #";

// INSERT
let cmdTextScriptInsert = "
    DECLARE @MainId INTEGER;
    DECLARE @SubId  INTEGER;

    @MainId = (SELECT MAX(Id)+1 FROM MainTable);
    @SubId  = (SELECT MAX(Id)+1 FROM SubTable );

    INSERT INTO MainTable (Id, Name, Value) VALUES (@MainId, :MainName, :MainValue);
    INSERT INTO SubTable (Id, Name, MainId, Value) VALUES (@SubId, :SubName, @MainId, :SubValue);

    SELECT @MainId, @SubId FROM system.iota;";
let cmdTextProcedureInsert = "CreateItems";

// UPDATE
let cmdTextScriptUpdate = "
    DECLARE @MainId INTEGER;

    @MainId = (SELECT TOP 1 Id  FROM MainTable WHERE Name = :MainName);

    UPDATE MainTable SET Value = :MainValue WHERE Id     = @MainId;
    UPDATE SubTable  SET Value = :SubValue  WHERE MainId = @MainId;";
let cmdTextProcedureUpdate = "UpdateItems";

// SELECT
let cmdTextScriptSelect = "
    SELECT m.Value * s.Value FROM MainTable m INNER JOIN SubTable s ON m.Id = s.MainId WHERE m.Name = :MainName;";
let cmdTextProcedureSelect = "SelectItems";

// DELETE
let cmdTextScriptDelete = "
    DECLARE @MainId INTEGER; 

    @MainId = (SELECT TOP 1 Id FROM MainTable WHERE Name = :MainName);

    DELETE FROM SubTable  WHERE MainId = @MainId;
    DELETE FROM MainTable WHERE Id     = @MainId;";
let cmdTextProcedureDelete = "DeleteItems";




let cnnStr = @"data source=D:\DB\test.add; ServerType=local; user id=adssys; password=***;";
let cnn = new AdsConnection(cnnStr);

try
    cnn.Open();

    let cmd = cnn.CreateCommand();

    let parametrize ix prms =
        cmd.Parameters.Clear();

        let addParam = function
            | "MainName"  -> cmd.Parameters.Add(":MainName" , mainName + ix.ToString()) |> ignore;
            | "SubName"   -> cmd.Parameters.Add(":SubName"  , subName + ix.ToString() ) |> ignore;
            | "MainValue" -> cmd.Parameters.Add(":MainValue", ix * 3                  ) |> ignore;
            | "SubValue"  -> cmd.Parameters.Add(":SubValue" , ix * 7                  ) |> ignore;
            | _ -> ()

        prms |> List.iter addParam;


    let runTest testData = 

        let (cmdType, cmdName, cmdText, cmdParams) = testData;

        let toPrefix cmdType cmdName =
            let prefix = match cmdType with
                | CommandType.StoredProcedure -> "Procedure-"
                | CommandType.Text            -> "Script   -"
                | _                           -> "Unknown  -"
            in prefix + cmdName;

        let stopWatch = new Stopwatch();

        let runStep ix prms =
            parametrize ix prms;
            stopWatch.Start();
            cmd.ExecuteNonQuery() |> ignore;
            stopWatch.Stop();

        cmd.CommandText <- cmdText;
        cmd.CommandType <- cmdType;

        let startId = 1500;
        let count = 10;

        for id in startId .. startId+count do
            runStep id cmdParams;

        let elapsed = stopWatch.Elapsed;
        Console.WriteLine("Test '{0}' - total: {1}; per call: {2}ms", toPrefix cmdType cmdName, elapsed, Convert.ToInt32(elapsed.TotalMilliseconds)/count);


    let lst = [
        (CommandType.Text,            "Insert", cmdTextScriptInsert,    ["MainName"; "SubName"; "MainValue"; "SubValue"]);
        (CommandType.Text,            "Update", cmdTextScriptUpdate,    ["MainName"; "MainValue"; "SubValue"]);
        (CommandType.Text,            "Select", cmdTextScriptSelect,    ["MainName"]);
        (CommandType.Text,            "Delete", cmdTextScriptDelete,    ["MainName"])
        (CommandType.StoredProcedure, "Insert", cmdTextProcedureInsert, ["MainName"; "SubName"; "MainValue"; "SubValue"]);
        (CommandType.StoredProcedure, "Update", cmdTextProcedureUpdate, ["MainName"; "MainValue"; "SubValue"]);
        (CommandType.StoredProcedure, "Select", cmdTextProcedureSelect, ["MainName"]);
        (CommandType.StoredProcedure, "Delete", cmdTextProcedureDelete, ["MainName"])];

    lst |> List.iter runTest;

finally
    cnn.Close();

И я получаю следующие результаты:

Тест 'Script -Insert' - всего: 00: 00: 00.0292841; за звонок: 2 мс

Тест 'Script -Update' - всего: 00: 00: 00.0056296; за звонок: 0 мс

Тест «Script -Select» - всего: 00: 00: 00.0051738; за звонок: 0 мс

Тест «Script -Delete» - всего: 00: 00: 00.0059258; за звонок: 0 мс

Тест «Процедура-вставка» - всего: 00: 00: 01.2567146; за звонок: 125мс

Тест «Процедура-Обновление» - всего: 00: 00: 00.7442440; за звонок: 74 мс

Тест «Выбор процедуры» - всего: 00: 00: 00.5120446; за звонок: 51мс

Тест «Процедура-Удаление» - всего: 00: 00: 01.0619165; за звонок: 106мс

Ситуация с удаленным сервером намного лучше, но все еще большой разрыв между простыми запросами и хранимыми процедурами:

Тест 'Script -Insert' - всего: 00: 00: 00.0709299; за звонок: 7 мс

Тест 'Script -Update' - всего: 00: 00: 00.0161777; за звонок: 1 мс

Тест 'Script -Select' - всего: 00: 00: 00.0258113; за звонок: 2 мс

Тест «Script -Delete» - всего: 00: 00: 00.0166242; за звонок: 1 мс

Тест «Процедура-вставка» - всего: 00: 00: 00.5116138; за звонок: 51мс

Тест «Процедура-Обновление» - всего: 00: 00: 00.3802251; за звонок: 38 мс

Тест «Выбор процедуры» - всего: 00: 00: 00.1241245; за звонок: 12 мс

Тест «Процедура-Удаление» - всего: 00: 00: 00.4336334; за звонок: 43мс

Есть ли шанс улучшить производительность SP? Пожалуйста, совет.

Версия драйвера ADO.NET - 9.10.2.9

Версия сервера - 9.10.0.9 (ANSI - НЕМЕЦКИЙ, OEM - НЕМЕЦКИЙ)

Спасибо!

Ответы [ 2 ]

6 голосов
/ 31 марта 2010

Advantage v10 beta включает множество улучшений производительности, непосредственно нацеленных на производительность хранимых процедур. Вот некоторые вещи, которые следует учитывать в текущей версии доставки:

В вашей процедуре CreateItems было бы более эффективно заменить

@MainName  = (SELECT MainName  FROM __input);
@SubName   = (SELECT SubName   FROM __input);
@MainValue = (SELECT MainValue FROM __input);
@SubValue  = (SELECT SubValue  FROM __input);

с использованием одного курсора для извлечения всех параметров:

DECLARE input CURSOR; 
OPEN input as SELECT * from __input;
FETCH input;
@MainName  = input.MainName;
@SubName   = input.SubName;
@MainValue = input.MainValue;
@SubValue  = input.SubValue;
CLOSE input;

Это позволит избежать 3 операций синтаксического анализа / семантики / оптимизации / выполнения оператора только для извлечения входных параметров (я знаю, нам действительно нужно полностью исключить таблицу __input).

Процедура SelectItems редко когда-либо будет такой же быстрой, как выборка с клиента, особенно в этом случае, когда она действительно ничего не делает, кроме абстрагирования значения параметра (что легко может быть выполнено на клиенте). Помните, что, поскольку это JOIN, SELECT для заполнения таблицы __output будет статическим курсором (имеется в виду внутренний временный файл, который сервер должен создавать и заполнять), но теперь, кроме того, у вас есть таблица __output, которая является еще одной временный файл для сервера, плюс у вас есть дополнительные издержки для заполнения этой таблицы __output данными, которые уже были помещены в временную таблицу статического курсора, просто для дублирования (сервер мог бы лучше обнаружить это и заменить __output с существующей ссылкой статического курсора, но в настоящее время это не так).

Я постараюсь потратить некоторое время, чтобы опробовать ваши процедуры в версии 10. Если у вас есть тестовые таблицы, которые вы использовали при тестировании, не стесняйтесь архивировать их, отправлять на Advantage@iAnywhere.com и помещать attn: JD субъект.

5 голосов
/ 01 апреля 2010

Есть одно изменение, которое поможет с процедурой CreateItems. Измените следующие два утверждения:

@MainId = (SELECT MAX(Id)+1 FROM MainTable);
@SubId  = (SELECT MAX(Id)+1 FROM SubTable );

К этому:

@MainId = (SELECT MAX(Id) FROM MainTable);
@MainId = @MainId + 1;
@SubId  = (SELECT MAX(Id) FROM SubTable );
@SubId  = @SubId + 1;

Я посмотрел информацию о плане запросов (в Advantage Data Architect) для первой версии этого оператора. Похоже, что оптимизатор не разбивает это MAX(id)+1 на составные части. Оператор select max(id) from maintable можно оптимизировать с помощью индекса в поле идентификатора. Похоже, что max(id)+1 не оптимизировано. Таким образом, внесение этого изменения будет весьма значительным, особенно по мере роста таблицы.

Другая вещь, которая может помочь, - это добавить оператор CACHE PREPARE ON; в начало каждого скрипта. Это может помочь с определенными процедурами при их многократном запуске.

Редактировать Сегодня вышла бета-версия Advantage v10 . Итак, я запустил вашу CreateItems процедуру как с v9.1, так и с новой бета-версией. Я провел 1000 итераций на удаленном сервере. Разница в скорости была значительной:

v9.1:      101 seconds
v10 beta:  2.2 seconds

Обратите внимание, что я запустил версию с изменением select max(id), которое я описал выше. Это тестирование проводилось на моем довольно старом ПК для разработки.

...