关于MYSQL DML(UPDATE DELETE)中的子查询问题和ERROR 1093 (HY000)错误

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 从5.6开始MYSQL的子查询进行了大量的优化,5.5中只有EXISTS strategy,在5.7中包含如下: IN(=ANY) --Semi-join   --table pullout(最快的,子查询条件为唯一键)   --first match   ...
从5.6开始MYSQL的子查询进行了大量的优化,5.5中只有EXISTS strategy,在5.7中包含如下:
IN(=ANY)
--Semi-join
  --table pullout(最快的,子查询条件为唯一键)
  --first match
  --semi-join materialization
  --loosescan
  --duplicateweedout
--Materialization
--EXISTS strategy(最慢的)
NOT IN( <>ALL)
--Materialization
--EXISTS strategy(最慢的)


而(not)exist却没有任何优化还是关联子查询的方式,这和ORACLE不一样,ORACLE中in、exists
都可以使用半连接(semi)优化.所以MYSQL中尽量使用in不要用exists。not in不能使用semi-join
要小心使用,更不要用not exists,关于上面每一个含义可以参考官方手册和mariadb手册。

我们简单的看一个列子,


使用semi-join materialization优化的
mysql> explain select * from testde1 where testde1.id in(select id from testde2);
+----+--------------+-------------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type  | table       | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                              |
+----+--------------+-------------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
|  1 | SIMPLE       | | NULL       | ALL  | NULL          | NULL | NULL    | NULL | NULL |   100.00 | NULL                                               |
|  1 | SIMPLE       | testde1     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   15 |    10.00 | Using where; Using join buffer (Block Nested Loop) |
|  2 | MATERIALIZED | testde2     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    2 |   100.00 | NULL                                               |
+----+--------------+-------------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
3 rows in set, 1 warning (0.00 sec)


Note (Code 1003): /* select#1 */ select `test`.`testde1`.`id` AS `id` from `test`.`testde1` semi join (`test`.`testde2`) where (`test`.`testde1`.`id` = ``.`id`)
semi join (`test`.`testde2`)  说明了问题


禁用semi join使用Materialization优化
mysql> set optimizer_switch='semijoin=off';
Query OK, 0 rows affected (0.00 sec)


mysql> explain select * from testde1 where testde1.id in(select id from testde2);
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | PRIMARY     | testde1 | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   15 |   100.00 | Using where |
|  2 | SUBQUERY    | testde2 | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    2 |   100.00 | NULL        |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)


Note (Code 1003): /* select#1 */ select `test`.`testde1`.`id` AS `id` from `test`.`testde1` where (`test`.`testde1`.`id`,`test`.`testde1`.`id` in ( (/* select#2 */ select `test`.`testde2`.`id` from `test`.`testde2` where 1 ), (`test`.`testde1`.`id` in on where ((`test`.`testde1`.`id` = `materialized-subquery`.`id`)))))


materialized-subquery`.`id`)说明了问题


禁用join使用Materialization
ysql> set optimizer_switch='materialization=off';
Query OK, 0 rows affected (0.00 sec)


mysql> explain select * from testde1 where testde1.id in(select id from testde2);
+----+--------------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type        | table   | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+--------------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | PRIMARY            | testde1 | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   15 |   100.00 | Using where |
|  2 | DEPENDENT SUBQUERY | testde2 | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    2 |    50.00 | Using where |
+----+--------------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)


Note (Code 1003): /* select#1 */ select `test`.`testde1`.`id` AS `id` from `test`.`testde1` where (`test`.`testde1`.`id`,(/* select#2 */ select 1 from `test`.`testde2` where ((`test`.`testde1`.`id`) = `test`.`testde2`.`id`)))


使用DEPENDENT SUBQUERY 关联子查询优化,这也是最慢的。这和
select * from testde1 where exists (select * from testde2 where testde1.id=testde2.id);的执行计划完全一致,
testde1大表必须作为驱动表
mysql> explain select * from testde1 where exists (select * from testde2 where testde1.id=testde2.id);
+----+--------------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type        | table   | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+--------------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | PRIMARY            | testde1 | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   15 |   100.00 | Using where |
|  2 | DEPENDENT SUBQUERY | testde2 | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    2 |    50.00 | Using where |
+----+--------------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
2 rows in set, 2 warnings (0.00 sec)


Note (Code 1276): Field or reference 'test.testde1.id' of SELECT #2 was resolved in SELECT #1
Note (Code 1003): /* select#1 */ select `test`.`testde1`.`id` AS `id` from `test`.`testde1` where exists(/* select#2 */ select 1 from `test`.`testde2` where (`test`.`testde1`.`id` = `test`.`testde2`.`id`))


同时在官方文档也说明了在DML中的子查询用不到SEMI优化和Materialization优化,只能使用exists言外之意就是只能使用关联子查询,转换为exists的格式。
那么速度可想而知,这种方式明显是外层表取出一行,驱动内层表一次,顺序固定,而jion的时候一般会选取小表作为驱动表性能更好。所以建议我们使用join
的方式来删除
原文如下:
A limitation on UPDATE and DELETE statements that use a subquery to modify a
single table is that the optimizer does not use semi-join or materialization subquery
optimizations. As a workaround, try rewriting them as multiple-table UPDATEand
DELETEstatements that use a join rather than a subquery.


实际就是下面的执行计划:


mysql> explain delete from testde1 where id in (select id from testde2);
+----+--------------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type        | table   | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+--------------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | DELETE             | testde1 | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   15 |   100.00 | Using where |
|  2 | DEPENDENT SUBQUERY | testde2 | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    2 |    50.00 | Using where |
+----+--------------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
2 rows in set (0.00 sec)


转换为了:
mysql> explain delete from testde1 where exists  (select * from testde2 where testde1.id=testde2.id);
+----+--------------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type        | table   | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+--------------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | DELETE             | testde1 | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   15 |   100.00 | Using where |
|  2 | DEPENDENT SUBQUERY | testde2 | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    2 |    50.00 | Using where |
+----+--------------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
可以看完全一样


应该使用:
mysql> explain delete  testde1 from testde1,testde2 where testde1.id=testde2.id;
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | testde2 | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    2 |   100.00 | NULL        |
|  1 | DELETE      | testde1 | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   15 |    10.00 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------------+
2 rows in set (0.00 sec)


这里我们看到小表testde2做了驱动表。
最后来说明一下这个报错:
mysql> delete from testde1 where id in(select testde1.id from testde1,testde2 where testde1.id=testde2.id );
ERROR 1093 (HY000): You can't specify target table 'testde1' for update in FROM clause
我们先不管他有没有意义,这个报错再手册上叫做ER_UPDATE_TABLE_USED,我们首先来分析一下这个报错
这样的delete会进行exists展开那么testde1既是修改条件的来源也是修改的对象,这样是不允许的。那么如何修改呢?
实际上就需要select testde1.id from testde1,testde2 where testde1.id=testde2.id 的结果保存在一个临时表中,
不要exists展开,手册中给出的方法是
方法一、建立一个algorithm=temptable 的视图
方法二、建立一个普通视图同时修改SET optimizer_switch = 'derived_merge=off';


其目的都在于不展开选取第二种方式测试:
mysql> create view myt1
    -> as
    -> select testde1.id from testde1,testde2 where testde1.id=testde2.id;
Query OK, 0 rows affected (0.02 sec)


mysql> delete from testde1 where id in (select * from myt1);
ERROR 1443 (HY000): The definition of table 'myt1' prevents operation DELETE on table 'testde1'.
mysql> SET optimizer_switch = 'derived_merge=off';
Query OK, 0 rows affected (0.00 sec)


mysql> delete from testde1 where id in (select * from myt1);
Query OK, 2 rows affected (0.00 sec)

mysql> flush status;
Query OK, 0 rows affected (0.02 sec)

mysql>  delete from testde1 where id in (select * from myt1);
Query OK, 2 rows affected (0.03 sec)

mysql> show status like '%tmp%';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| Created_tmp_disk_tables | 0     |
| Created_tmp_files       | 0     |
| Created_tmp_tables      | 2    |
+-------------------------+-------+
3 rows in set (0.01 sec)

看看执行计划:
mysql> explain delete from testde1 where id in (select * from myt1);
+----+--------------------+------------+------------+----------------+---------------+-------------+---------+------+------+----------+----------------------------------------------------+
| id | select_type        | table      | partitions | type           | possible_keys | key         | key_len | ref  | rows | filtered | Extra                                              |
+----+--------------------+------------+------------+----------------+---------------+-------------+---------+------+------+----------+----------------------------------------------------+
|  1 | DELETE             | testde1    | NULL       | ALL            | NULL          | NULL        | NULL    | NULL |   13 |   100.00 | Using where                                        |
|  2 | DEPENDENT SUBQUERY | | NULL       | index_subquery |   | | 5       | func |    2 |   100.00 | Using index                                        |
|  3 | DERIVED            | testde2    | NULL       | ALL            | NULL          | NULL        | NULL    | NULL |    2 |   100.00 | NULL                                               |
|  3 | DERIVED            | testde1    | NULL       | ALL            | NULL          | NULL        | NULL    | NULL |   13 |    10.00 | Using where; Using join buffer (Block Nested Loop) |
+----+--------------------+------------+------------+----------------+---------------+-------------+---------+------+------+----------+----------------------------------------------------+
4 rows in set (0.00 sec)


可以看到子查询作为了一个整体,从status和执行计划dervied都可以看到使用了临时表,这样可行,但是性能上肯定不好。
在ORACLE中不存在这样的问题,执行计划如下:
SQL>  delete from testde1 where id in(select testde1.id from testde1,testde2 where testde1.id=testde2.id );
0 rows deleted.
Execution Plan
----------------------------------------------------------
Plan hash value: 2653154564
--------------------------------------------------------------------------------
| Id  | Operation             | Name     | Rows  | Bytes | Cost (%CPU)| Time
---------------------------------------------------------------------------------
|   0 | DELETE STATEMENT      |          |     1 |    26 |     7  (15)| 00:00:01|
|   1 |  DELETE               | TESTDE1  |       |       |            |         |
|*  2 |   HASH JOIN SEMI      |          |     1 |    26 |     7  (15)| 00:00:01|
|   3 |    TABLE ACCESS FULL  | TESTDE1  |     5 |    65 |     2   (0)| 00:00:01|
|   4 |    VIEW               | VW_NSO_1 |     1 |    13 |     5  (20)| 00:00:01|
|*  5 |     HASH JOIN         |          |     1 |    26 |     5  (20)| 00:00:01|
|   6 |      TABLE ACCESS FULL| TESTDE2  |     1 |    13 |     2   (0)| 00:00:01|
|   7 |      TABLE ACCESS FULL| TESTDE1  |     5 |    65 |     2   (0)| 00:00:01|
---------------------------------------------------------------------------------

先使用hash join将 TESTDE2 和 TESTDE1  建立为一个视图 VW_NSO_1,然后使用了 HASH JOIN SEMI的优化方式,明显用了到半连接优化
这也是为什么ORACLE比现在的MYSQL还是更加强劲的一个小例子,虽然都是作为一个整体,但是MYSQL已经用不到SEMI优化方式了,ORACLE
依然可以,但是可以预见不久的将来MYSQL肯定支持的。


最后总结一下:
1、.所以MYSQL中尽量使用in不要用exists。not in不能使用semi-join要小心使用,更不要用not exists
2、子查询DML应该修改关联DML(update delete)
3、ERROR 1093 (HY000)错误原因是 某张表既是修改的对象也是信息来源的对象。需要使用algorithm=temptable或者
   optimizer_switch = 'derived_merge=off'的方式。
   

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
4月前
|
SQL 关系型数据库 MySQL
在 MySQL 中使用子查询
【8月更文挑战第12天】
231 0
在 MySQL 中使用子查询
|
1月前
|
SQL 关系型数据库 MySQL
|
1月前
|
网络协议 关系型数据库 MySQL
MySQL报ERROR 2002 (HY000)解决
通过上述步骤,可以有效地解决MySQL连接时出现的 `ERROR 2002 (HY000)`错误。这些步骤包括检查和启动MySQL服务、配置文件检查、套接字文件检查、日志文件分析、进程检查、防火墙设置、客户端配置和最终的MySQL重装。确保每个步骤都按顺序执行,有助于快速定位和解决问题,使MySQL服务器恢复正常运行。
537 0
|
3月前
|
SQL 缓存 关系型数据库
MySQL高级篇——关联查询和子查询优化
左外连接:优先右表创建索引,连接字段类型要一致、内连接:驱动表由数据量和索引决定、 join语句原理、子查询优化:拆开查询或优化成连接查询
MySQL高级篇——关联查询和子查询优化
|
2月前
|
SQL 关系型数据库 MySQL
MySQL异常一之: You can‘t specify target table for update in FROM clause解决办法
这篇文章介绍了如何解决MySQL中“不能在FROM子句中指定更新的目标表”(You can't specify target table for update in FROM clause)的错误,提供了错误描述、需求说明、错误做法和正确的SQL写法。
656 0
|
4月前
|
关系型数据库 MySQL Shell
MySQL回滚脚本: 误操作delete binlog回滚shell脚本
MySQL回滚脚本: 误操作delete binlog回滚shell脚本
|
4月前
|
存储 SQL 关系型数据库
MySQL中的update操作与锁机制
本文探讨MySQL中`UPDATE`操作的自动加锁机制及其对数据一致性的保障作用。尤其在InnoDB存储引擎下,系统会在涉及索引的更新操作中加行锁或间隙锁,防止多事务并发修改同一条记录。通过福利码兑换系统的实例展示,当线程A开启事务更新库存时,线程B试图更新相同记录会被阻塞,直至线程A提交。此外,文章还介绍了乐观锁及版本号控制等策略进一步提升并发性能的方法。作者:小明爱吃火锅,来源:稀土掘金。
236 2
|
5月前
|
SQL 关系型数据库 MySQL
MySQL删除表数据、清空表命令(truncate、drop、delete 区别)
MySQL删除表数据、清空表命令(truncate、drop、delete区别) 使用原则总结如下: 当你不需要该表时(删除数据和结构),用drop; 当你仍要保留该表、仅删除所有数据表内容时,用truncate; 当你要删除部分记录、且希望能回滚的话,用delete;
|
5月前
|
SQL Java 数据库
MySQL设计规约问题之为什么应尽量避免使用子查询,而可以考虑将其优化为join操作
MySQL设计规约问题之为什么应尽量避免使用子查询,而可以考虑将其优化为join操作
|
5月前
|
SQL Oracle 关系型数据库
实时计算 Flink版产品使用问题之在从MySQL同步数据到Doris时,delete语句无法同步,是什么原因
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。