Advantage Database Server:缓慢的存储过程性能

时间:2010-03-30 14:57:58

标签: stored-procedures advantage-database-server

我对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;

实际上,我遇到的问题 - 即使是如此轻的存储过程相对于普通查询(0-5ms)工作得非常慢(大约50-150毫秒)。为了测试性能,我创建了一个简单的测试(在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();

我得到以下结果:

  

测试'脚本 - 插入' - 总计:00:00:00.0292841;每次通话:2ms

     

测试'脚本 - 更新' - 总计:00:00:00.0056296;每次通话:0ms

     

测试'脚本 - 选择' - 总计:00:00:00.0051738;每次通话:0ms

     

测试'Script -Delete' - 总计:00:00:00.0059258;每次通话:0ms

     

测试'程序 - 插入' - 总计:00:00:01.2567146;每次通话:125毫秒

     

测试'程序 - 更新' - 总计:00:00:00.7442440;每次通话:74毫秒

     

测试'程序 - 选择' - 总计:00:00:00.5120446;每次通话:51ms

     

测试'程序 - 删除' - 总计:00:00:01.0619165;每次通话:106毫秒

远程服务器的情况好多了,但是plaqin查询和存储过程之间的差距仍然很大:

  

测试'脚本 - 插入' - 总计:00:00:00.0709299;每次通话:7毫秒

     

测试'脚本 - 更新' - 总计:00:00:00.0161777;每次通话:1ms

     

测试'脚本 - 选择' - 总计:00:00:00.0258113;每次通话:2ms

     

测试'Script -Delete' - 总计:00:00:00.0166242;每次通话:1ms

     

测试'程序 - 插入' - 总计:00:00:00.5116138;每次通话:51ms

     

测试'程序 - 更新' - 总计:00:00:00.3802251;每次通话:38毫秒

     

测试'程序 - 选择' - 总计:00:00:00.1241245;每次通话:12ms

     

测试'程序 - 删除' - 总计:00:00:00.4336334;每次通话:43毫秒

是否有机会改善SP性能?请指教。

ADO.NET驱动程序版本 - 9.10.2.9

服务器版本 - 9.10.0.9(ANSI - 德语,OEM - 德语)

谢谢!

2 个答案:

答案 0 :(得分:6)

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过程很少会像客户端中的select一样快,特别是在这种情况下,除了抽象参数值(可以在客户端轻松完成)之外,它实际上没有做任何事情。请记住,因为它是一个JOIN,所以填充__output表的SELECT将是一个静态游标(意味着服务器要创建和填充的内部临时文件),但现在另外还有__output表,这是另一个服务器的临时文件,加上你有额外的开销用这个__output表填充已经放在静态游标临时表中的数据,只是为了复制它(服务器可以更好地检测这个并替换__output使用现有的静态游标引用,但它目前没有)。

我会尝试花点时间在第10版上尝试你的程序。如果你有测试表,你可以随意将它们压缩并发送到Advantage@iAnywhere.com并放入attn:JD in主题。

答案 1 :(得分:5)

有一项更改可以帮助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分解为组件。可以使用ID字段上的索引优化语句select max(id) from maintable。似乎max(id)+1未经优化。因此,当表格增长时,进行这种改变将是相当重要的。

可能有用的另一件事是在每个脚本的顶部添加CACHE PREPARE ON;语句。这可以在多次运行时帮助执行某些过程。

修改 Advantage v10 beta今天发布了。所以我用v9.1和新的beta版本运行了CreateItems程序。我对远程服务器运行了1000次迭代。速度差异显着:

v9.1:      101 seconds
v10 beta:  2.2 seconds

请注意,我运行了上述select max(id)更改的版本。这个测试是在我相当老的开发PC上进行的。