【问题标题】:mysql stored procedure is slower 20 times than standard querymysql存储过程比标准查询慢20倍
【发布时间】:2013-05-25 18:58:34
【问题描述】:

我有 10 个表,除了表名之外结构相同。

我有一个 sp(存储过程)定义如下:

 select * from table1 where (@param1 IS NULL OR col1=@param1)
 UNION ALL
 select * from table2 where (@param1 IS NULL OR col1=@param1)
 UNION ALL
 ...
 ...
 UNION ALL
 select * from table10 where (@param1 IS NULL OR col1=@param1)

我用以下行调用 sp:

call mySP('test')  //it executes in 6,836s

然后我打开了一个新的标准查询窗口。我刚刚复制了上面的查询。然后将@param1 替换为'test'。

这在 0,321 秒内执行,比存储过程快大约 20 倍。

为了防止结果被缓存,我反复更改了参数值。但这并没有改变结果。 SP 比同等标准查询慢约 20 倍。

请你帮我弄清楚为什么会这样?

有没有人遇到过类似的问题?

我在 windows server 2008 R2 64 位上使用 mySQL 5.0.51。

编辑:我正在使用 Navicat 进行测试。

任何想法都会对我有所帮助。

编辑1:

我刚刚根据 Barmar 的回答做了一些测试。

最后我把 sp 改成了下面的一行:

 SELECT * FROM table1 WHERE col1=@param1 AND col2=@param2

然后我首先执行标准查询

 SELECT * FROM table1 WHERE col1='test' AND col2='test'  //Executed in 0.020s

在我调用了我的 sp 之后:

 CALL MySp('test','test')    //Executed in 0.466s

所以我完全改变了 where 子句,但没有任何改变。我从mysql命令窗口而不是navicat调用了sp。它给出了相同的结果。我仍然坚持下去。

我的 sp ddl:

 CREATE DEFINER = `myDbName`@`%`
 PROCEDURE `MySP` (param1 VARCHAR(100), param2 VARCHAR(100))
 BEGIN
    SELECT * FROM table1 WHERE col1=param1 AND col2=param2
 END

并且 col1 和 col2 被合并索引。

你可以说那你为什么不使用标准查询呢?我的软件设计不适合这个。我必须使用存储过程。所以这个问题对我来说非常重要。

EDIT2:

我已获得查询个人资料信息。很大的不同是因为 SP Profile Information 中的“发送数据行”。发送数据部分需要 %99 的查询执行时间。我正在本地数据库服务器上进行测试。我没有从远程计算机连接。

SP 个人资料信息

查询个人资料信息

我已经在我的 sp 中尝试了如下所示的强制索引语句。但结果相同。

 SELECT * FROM table1 FORCE INDEX (col1_col2_combined_index) WHERE col1=@param1 AND col2=@param2

我已经改变了 sp 如下。

 EXPLAIN SELECT * FROM table1 FORCE INDEX (col1_col2_combined_index) WHERE col1=param1 AND col2=param2

这给出了这个结果:

 id:1
 select_type=SIMPLE
 table:table1
 type=ref
 possible_keys:NULL
 key:NULL
 key_len:NULL
 ref:NULL
 rows:292004
 Extra:Using where

然后我执行了下面的查询。

 EXPLAIN SELECT * FROM table1 WHERE col1='test' AND col2='test'

结果是:

 id:1
 select_type=SIMPLE
 table:table1
 type=ref
 possible_keys:col1_co2_combined_index
 key:col1_co2_combined_index
 key_len:76
 ref:const,const
 rows:292004
 Extra:Using where

我在 SP 中使用 FORCE INDEX 语句。但它坚持不使用索引。任何的想法?我想我快结束了:)

【问题讨论】:

  • 可能是执行SP后,MySQL已经缓存了结果,然后在SP外执行时,只是命中缓存而不是再次执行。
  • 对了,为什么10个表结构一样?为什么不将它们合并到一张表中?
  • 数据库设计超出了我的掌控,我永远不会做这样的设计:) 首先我使用不同的参数执行查询,然后立即使用相同的参数调用 sp。结果相同。似乎 sp 甚至没有从缓存中获取结果。
  • 当您将其作为普通查询执行时,您是否将每个子查询上出现的@param1 都替换为“test”,或者只使用 (col1 = 'test')。如果是这样怀疑mysql可能由于OR而无法在存储过程中使用索引,但在查询中使用了一个
  • 带有FORCE INDEX 的SP 工作得更快吗?查询优化器可能不如其他数据库那么好,并创建计划 before 用它们的值替换参数

标签: mysql stored-procedures


【解决方案1】:

只是猜测:

当您手动运行查询时,可以在解析查询时优化表达式WHERE ('test' IS NULL or COL1 = 'test')。解析器可以看到字符串'test' 不为空,因此它将测试转换为WHERE COL1 = 'test'。如果COL1 上有索引,则会使用它。

但是,当您创建存储过程时,会在创建过程时进行解析。到时候还不知道@param会是什么,只好将查询实现为对表的顺序扫描。

尝试将您的程序更改为:

IF @param IS NULL
THEN BEGIN
  SELECT * FROM table1
  UNION ALL
  SELECT * FROM table2
  ...
END;
ELSE BEGIN
  SELECT * FROM table1 WHERE col1 = @param
  UNION ALL
  SELECT * FROM table2 WHERE col1 = @param
  ...
END;
END IF;

我对 MySQL 存储过程没有太多经验,所以我不确定这是否是正确的语法。

【讨论】:

  • 感谢您的回答。我会试一试。实际上我的 where 子句并没有那么短。 (@param1 为 null 或 col1=@param1)和(@param2 为 null 或 col2=@param2)和 .... 到 param6。为了清楚起见,我没有写得那么详细。对不起。我认为在这种情况下,您的解决方案并不能完全涵盖我的问题。
  • 不同之处在于将变量用作@Var is null or col1=@var。在两者上运行 EXPLAIN:EXPLAIN SELECT * FROM MY_VIEWSELECT * FROM SP_MY_PROC。它应该给出原因。在 SQL Server 中有一个选项 RECOMPILE 在这种情况下用于重新编译执行计划。我正在寻找但没有在 MySQL 中找到类似的东西。
  • @Stoleg 这是我最初的解释,但他在最新版本中去掉了OR。所以我不知道为什么SP仍然会很慢。
  • @Barmar,对不起。 EXPLAIN 应该给出一些关于原因的想法。实际上,从提供的时间来看,在参数的情况下创建查询计划可能需要很长时间。 Read about Controlling Plan Evaluation。创建执行计划然后执行查询本身可能需要更长的时间。
  • @Barmar,您无法在 SP 上运行解释。但是,您可以使用多个联合在查询上创建视图或运行解释。从时间上看,在我看来,这是一个耗时太长的预执行阶段。
【解决方案2】:

可能的字符集问题?如果您的表字符集与数据库字符集不同,这可能会导致问题。

查看此错误报告:http://bugs.mysql.com/bug.php?id=26224

[2007 年 11 月 12 日 21:32] Mark Kubacki 5.1.22_rc 仍然没有运气 - 键 被输入,查询在一个过程中需要 36 秒和外部 0.12 秒。

[2007 年 11 月 12 日 22:30] Mark Kubacki 在将字符集更改为 UTF-8(尤其是使用的两个)之后,用于 无论如何连接,密钥都被考虑在存储的 程序!

我无法回答的问题是:优化器为什么要处理 charset 在存储过程内部和外部以其他方式转换? (确实,我问这个可能是错的。)

【讨论】:

  • 非常感谢。原因完全一样。我的表和数据库字符集不同。我把它们都改成一样了,那么索引已经用在sp了。
【解决方案3】:

有趣的问题,因为我喜欢使用存储过程。原因是维护和封装原则。

这是我找到的信息: http://dev.mysql.com/doc/refman/5.1/en/query-cache-operation.html

它声明查询缓存不用于以下查询 1. 是属于外部查询的子查询,并且 2. 在存储过程、触发器或事件的主体内执行。

这意味着它按设计工作。

【讨论】:

    【解决方案4】:

    我见过这种行为,但它与字符集无关。

    我有一个包含自引用分层数据的表(父母有孩子,有些孩子有自己的孩子,等等)。由于 parent_id 必须引用主 ID(并且该列指定了该效果的约束),因此我无法将父 ID 设置为 NULL 或 0(零)以将子 ID 与父母分开,因此我只是将其引用为自己。

    当我运行一个存储过程来执行递归查询以查找特定父级的所有子级(在所有级别)时,该查询的运行时间是 30 到 40 倍。我发现更改存储过程使用的查询以确保它排除顶级父记录(通过指定 WHERE parent_id != id)恢复了查询的性能。

    我使用的存储过程基于如下所示: https://stackoverflow.com/questions/27013093/recursive-query-emulation-in-mysql.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-02-23
      • 1970-01-01
      • 1970-01-01
      • 2015-01-03
      • 2013-12-07
      • 1970-01-01
      • 2012-10-16
      相关资源
      最近更新 更多