MySQL:delete…where..in()vs delete..from..join,并使用subselect锁定删除表


9

免责声明:请原谅我缺乏数据库内部知识。它去了:

我们运行的应用程序(不是我们编写的)在数据库的定期清理作业中存在很大的性能问题。查询如下所示:

delete from VARIABLE_SUBSTITUTION where BUILDRESULTSUMMARY_ID in (
       select BUILDRESULTSUMMARY_ID from BUILDRESULTSUMMARY
       where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1");

直截了当,易于阅读和标准SQL。但不幸的是非常缓慢。对查询进行解释说明VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_ID未使用现有索引on :

mysql> explain delete from VARIABLE_SUBSTITUTION where BUILDRESULTSUMMARY_ID in (
    ->        select BUILDRESULTSUMMARY_ID from BUILDRESULTSUMMARY
    ->        where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1");
| id | select_type        | table                 | type            | possible_keys                    | key     | key_len | ref  | rows    | Extra       |
+----+--------------------+-----------------------+-----------------+----------------------------------+---------+---------+------+---------+-------------+
|  1 | PRIMARY            | VARIABLE_SUBSTITUTION | ALL             | NULL                             | NULL    | NULL    | NULL | 7300039 | Using where |
|  2 | DEPENDENT SUBQUERY | BUILDRESULTSUMMARY    | unique_subquery | PRIMARY,key_number_results_index | PRIMARY | 8       | func |       1 | Using where |

这使其非常慢(120秒以上)。除此之外,它似乎是试图插入块查询BUILDRESULTSUMMARY,从输出show engine innodb status

---TRANSACTION 68603695, ACTIVE 157 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s)
MySQL thread id 127964, OS thread handle 0x7facd0670700, query id 956555826 localhost 127.0.0.1 bamboosrv updating
update BUILDRESULTSUMMARY set CREATED_DATE='2015-06-18 09:22:05', UPDATED_DATE='2015-06-18 09:22:32', BUILD_KEY='BLA-RELEASE1-JOB1', BUILD_NUMBER=8, BUILD_STATE='Unknown', LIFE_CYCLE_STATE='InProgress', BUILD_DATE='2015-06-18 09:22:31.792', BUILD_CANCELLED_DATE=null, BUILD_COMPLETED_DATE='2015-06-18 09:52:02.483', DURATION=1770691, PROCESSING_DURATION=1770691, TIME_TO_FIX=null, TRIGGER_REASON='com.atlassian.bamboo.plugin.system.triggerReason:CodeChangedTriggerReason', DELTA_STATE=null, BUILD_AGENT_ID=199688199, STAGERESULT_ID=230943366, RESTART_COUNT=0, QUEUE_TIME='2015-06-18 09:22:04.52
------- TRX HAS BEEN WAITING 157 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 38 page no 30140 n bits 112 index `PRIMARY` of table `bamboong`.`BUILDRESULTSUMMARY` trx id 68603695 lock_mode X locks rec but not gap waiting
------------------
---TRANSACTION 68594818, ACTIVE 378 sec starting index read
mysql tables in use 2, locked 2
646590 lock struct(s), heap size 63993384, 3775190 row lock(s), undo log entries 117
MySQL thread id 127845, OS thread handle 0x7facc6bf8700, query id 956652201 localhost 127.0.0.1 bamboosrv preparing
delete from VARIABLE_SUBSTITUTION  where BUILDRESULTSUMMARY_ID in   (select BUILDRESULTSUMMARY_ID from BUILDRESULTSUMMARY where BUILDRESULTSUMMARY.BUILD_KEY = 'BLA-BLUBB10-SON')

这减慢了系统速度,迫使我们增加速度innodb_lock_wait_timeout

在运行MySQL时,我们重写了delete查询以使用“从连接中删除”:

delete VARIABLE_SUBSTITUTION from VARIABLE_SUBSTITUTION join BUILDRESULTSUMMARY
   on VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_ID = BUILDRESULTSUMMARY.BUILDRESULTSUMMARY_ID
   where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1";

这一点不太容易阅读,不幸的是没有标准的SQL(据我所知),但是使用索引却快得多(0.02秒左右):

mysql> explain delete VARIABLE_SUBSTITUTION from VARIABLE_SUBSTITUTION join BUILDRESULTSUMMARY
    ->    on VARIABLE_SUBSTITUTION.BUILDRESULTSUMMARY_ID = BUILDRESULTSUMMARY.BUILDRESULTSUMMARY_ID
    ->    where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1";
| id | select_type | table                 | type | possible_keys                    | key                      | key_len | ref                                                    | rows | Extra                    |
+----+-------------+-----------------------+------+----------------------------------+--------------------------+---------+--------------------------------------------------------+------+--------------------------+
|  1 | SIMPLE      | BUILDRESULTSUMMARY    | ref  | PRIMARY,key_number_results_index | key_number_results_index | 768     | const                                                  |    1 | Using where; Using index |
|  1 | SIMPLE      | VARIABLE_SUBSTITUTION | ref  | var_subst_result_idx             | var_subst_result_idx     | 8       | bamboo_latest.BUILDRESULTSUMMARY.BUILDRESULTSUMMARY_ID |   26 | NULL                     |

附加信息:

mysql> SHOW CREATE TABLE VARIABLE_SUBSTITUTION;
| Table                 | Create Table |
| VARIABLE_SUBSTITUTION | CREATE TABLE `VARIABLE_SUBSTITUTION` (
  `VARIABLE_SUBSTITUTION_ID` bigint(20) NOT NULL,
  `VARIABLE_KEY` varchar(255) COLLATE utf8_bin NOT NULL,
  `VARIABLE_VALUE` varchar(4000) COLLATE utf8_bin DEFAULT NULL,
  `VARIABLE_TYPE` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `BUILDRESULTSUMMARY_ID` bigint(20) NOT NULL,
  PRIMARY KEY (`VARIABLE_SUBSTITUTION_ID`),
  KEY `var_subst_result_idx` (`BUILDRESULTSUMMARY_ID`),
  KEY `var_subst_type_idx` (`VARIABLE_TYPE`),
  CONSTRAINT `FK684A7BE0A958B29F` FOREIGN KEY (`BUILDRESULTSUMMARY_ID`) REFERENCES `BUILDRESULTSUMMARY` (`BUILDRESULTSUMMARY_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin |

mysql> SHOW CREATE TABLE BUILDRESULTSUMMARY;
| Table              | Create Table |
| BUILDRESULTSUMMARY | CREATE TABLE `BUILDRESULTSUMMARY` (
  `BUILDRESULTSUMMARY_ID` bigint(20) NOT NULL,
....
  `SKIPPED_TEST_COUNT` int(11) DEFAULT NULL,
  PRIMARY KEY (`BUILDRESULTSUMMARY_ID`),
  KEY `FK26506D3B9E6537B` (`CHAIN_RESULT`),
  KEY `FK26506D3BCCACF65` (`MERGERESULT_ID`),
  KEY `key_number_delta_state` (`DELTA_STATE`),
  KEY `brs_build_state_idx` (`BUILD_STATE`),
  KEY `brs_life_cycle_state_idx` (`LIFE_CYCLE_STATE`),
  KEY `brs_deletion_idx` (`MARKED_FOR_DELETION`),
  KEY `brs_stage_result_id_idx` (`STAGERESULT_ID`),
  KEY `key_number_results_index` (`BUILD_KEY`,`BUILD_NUMBER`),
  KEY `brs_agent_idx` (`BUILD_AGENT_ID`),
  KEY `rs_ctx_baseline_idx` (`VARIABLE_CONTEXT_BASELINE_ID`),
  KEY `brs_chain_result_summary_idx` (`CHAIN_RESULT`),
  KEY `brs_log_size_idx` (`LOG_SIZE`),
  CONSTRAINT `FK26506D3B9E6537B` FOREIGN KEY (`CHAIN_RESULT`) REFERENCES `BUILDRESULTSUMMARY` (`BUILDRESULTSUMMARY_ID`),
  CONSTRAINT `FK26506D3BCCACF65` FOREIGN KEY (`MERGERESULT_ID`) REFERENCES `MERGE_RESULT` (`MERGERESULT_ID`),
  CONSTRAINT `FK26506D3BCEDEEF5F` FOREIGN KEY (`STAGERESULT_ID`) REFERENCES `CHAIN_STAGE_RESULT` (`STAGERESULT_ID`),
  CONSTRAINT `FK26506D3BE3B5B062` FOREIGN KEY (`VARIABLE_CONTEXT_BASELINE_ID`) REFERENCES `VARIABLE_CONTEXT_BASELINE` (`VARIABLE_CONTEXT_BASELINE_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin |

(省略了一些东西,这是一个很大的表)。

因此,我对此有一些疑问:

  • 为什么查询优化器在使用联接版本时不能使用索引来删除子查询版本?
  • 是否有任何(完全符合标准的)方法来欺骗它使用索引?要么
  • 有没有一种便携式的方式来编写delete from join?该应用程序支持通过jdbc和Hibernate使用的PostgreSQL,MySQL,Oracle和Microsoft SQL Server。
  • 为什么从VARIABLE_SUBSTITUTION阻塞插入中删除BUILDRESULTSUMMARY仅插入到subselect中?

Percona Server 5.6.24-72.2-1.jessie resp 5.6.24-72.2-1.wheezy(在测试系统上)。
0x89上

是的,整个数据库都使用innodb。
2015年

因此,似乎5.6在改进优化程序方面并没有引起太多关注。您必须等待5.7(但如果可以,请尝试使用MariaDB。他们的优化程序改进已在其5.3和5.5版本中完成。)
ypercubeᵀᴹ2015年

@ypercube AFAIK no fork没有进行增强,以使delete子查询优化为5.7。删除优化与SELECT语句不同。
Morgan Tocker

Answers:


7
  • 为什么查询优化器在使用联接版本时不能使用索引来删除子查询版本?

因为优化器在这方面有点笨。不仅对于语句DELETEUPDATE而且对于SELECT语句,诸如此类的东西WHERE column IN (SELECT ...)都没有得到完全优化。执行计划通常涉及为外部表的每一行运行子查询(VARIABLE_SUBSTITUTION在这种情况下)。如果那个桌子很小,那一切都很好。如果很大,就没有希望了。在甚至更老的版本中,带有IN子子查询的IN子查询也会使子查询EXPLAIN运行一段时间。

您可以做的-如果要保留此查询-就是使用已实现多项优化的最新版本,然后再次进行测试。最新版本的意思是:MySQL 5.6(从beta发行时为5.7)和MariaDB 5.5 / 10.0

(更新)您已经使用5.6进行了优化改进,而这与5.6相关:通过半联接转换优化子查询
我建议(BUILD_KEY)单独添加一个索引。有一个复合的查询,但是对于此查询来说不是很有用。

  • 是否有任何(完全符合标准的)方法来欺骗它使用索引?

我没有想到的。我认为,尝试使用标准SQL并没有太多价值。有这么多的差异和轻微的怪癖,每个DBMS都(UPDATEDELETE陈述的这种差异很好的例子),当您尝试使用一些作品随处可见,其结果是SQL的一个非常有限的子集。

  • 有没有一种可移植的方法来编写从联接中删除的操作?该应用程序支持通过jdbc和Hibernate使用的PostgreSQL,MySQL,Oracle和Microsoft SQL Server。

与上一个问题的答案相同。

  • 为什么从VARIABLE_SUBSTITUTION阻止删除插入到BUILDRESULTSUMMARY中,仅在subselect中使用?

不能100%确定,但是我认为这与多次运行子查询以及它对表采取的锁类型有关。


来自innodb_status(正在删除的事务)的“ 3775190行锁”极具启发性。而且“在使用2 MySQL表,锁定2”不看我太好了..
0x89上

2

这是您的两个问题的答案

  • Optimizer无法使用索引,因为where子句对于每一行都会更改。通过优化程序后,delete语句将类似于以下内容

    delete from VARIABLE_SUBSTITUTION where EXISTS (
    select BUILDRESULTSUMMARY_ID from BUILDRESULTSUMMARY
    where BUILDRESULTSUMMARY.BUILD_KEY = BUILDRESULTSUMMARY_ID AND BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1");

但是当您进行连接时,服务器能够识别要删除的行。

  • 技巧是使用变量来保存BUILDRESULTSUMMARY_ID和使用变量而不是查询。请注意,变量初始化和删除查询都必须在会话中运行。这样的东西。

    SET @ids = (SELECT GROUP_CONCAT(BUILDRESULTSUMMARY_ID) 
            from BUILDRESULTSUMMARY where BUILDRESULTSUMMARY.BUILD_KEY = "BAM-1" ); 
    delete from VARIABLE_SUBSTITUTION where FIND_IN_SET(BUILDRESULTSUMMARY_ID,@ids) > 0;

    如果查询返回的ID过多,这可能不是问题,这不是标准方法。这只是一个解决方法。

    我还没有其他两个问题的答案:)


好吧,你错过了我的意思。我认为您没有考虑的是VARIABLE_SUBSTITUTION和BUILDRESULTSUMMARY都有一个名为BUILDRESULTSUMMARY_ID的列,因此它应该是:'从存在的VARIABLE_SUBSTITUTION中删除(从BUILDRESULTSUMMARY的BUILDRESULTSUMMARY_ID中选择BUILDRESULTSUMMARY_MMID_BUILDIDULT_MMID_RESULTARYSMMARYRESMARY_RESIDARYBUMMID_BUILDING_SUMMARYRESIDARYSUMMARY ===。 -1“);'。然后这很有意义,并且两个查询都执行相同的操作。
2015年

1
是的,我只是缺少对外部表的引用。但这不是重点。那只是说明如何在优化器中对其进行处理。
Masoud

与优化器会产生等效查询的方式稍有不同。
2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.