在 MySQL 中使用 DISTINCT 时如何摆脱使用临时?

bbi*_*ras 4 mysql performance query-performance

当我执行以下操作时,我得到Using where; Using index; Using temporary. 我该如何摆脱Using temporary

http://sqlfiddle.com/#!8/f61a2/3/0

CREATE TABLE `test` (
  `a` varchar(45) NOT NULL,
  `b` varchar(45) NOT NULL,
  `date` date NOT NULL,
  PRIMARY KEY (`a`,`b`),
  KEY `index1` (`b`,`date`),
  KEY `index2` (`b`,`date`,`a`)
) ENGINE=InnoDB;

INSERT INTO test (a, b, date) VALUES ('a1', 'b1', now());
INSERT INTO test (a, b, date) VALUES ('a1', 'b2', now());
INSERT INTO test (a, b, date) VALUES ('a2', 'b1', now());
INSERT INTO test (a, b, date) VALUES ('a2', 'b2', now());

INSERT INTO test (a, b, date) VALUES ('a3', 'b1', '2000-01-01 01:01:01');
INSERT INTO test (a, b, date) VALUES ('a3', 'b2', '2000-01-01 01:01:01');
Run Code Online (Sandbox Code Playgroud)
EXPLAIN SELECT
DISTINCT a
FROM test
WHERE b IN ('b1', 'b2') AND
date > NOW() - INTERVAL 1 MONTH;
Run Code Online (Sandbox Code Playgroud)

Ray*_*and 5

您是否有性能问题,因为这听起来像是过早的优化?

使用索引的唯一好的单个查询是 UNION(它也将过滤掉重复项)。

如果您EXPLAIN在此查询上运行,则说明中不会弹出using 临时..

SELECT
 a
FROM test
WHERE b = 'b1' AND
date > NOW() - INTERVAL 1 MONTH

UNION

SELECT
 a
FROM test
WHERE b = 'b2' AND
date > NOW() - INTERVAL 1 MONTH
;
Run Code Online (Sandbox Code Playgroud)

已编辑 2019-09-01

演示不再有效,因为sqlfiddle不再运行旧的 MySQL 版本,并且因为 MySQL 源代码已更新...

我建议查看db-fiddle并在 MySQL 版本 5.5/5.6/5.7 之间切换,然后问题应该在 5.5 和 5.6 之间可见

通过查看sql/union.ccMySQL 5.7.2 m12 版本的源代码文件(C++ 代码)(很可能(所有)较低版本也使用此代码,但不确定),我们可以看到 UNION 在 MySQL 中的工作方式。您会发现EXPLAIN 输出有时会告诉您谎言,因为源代码(在输入参数之一中)表明:

is_union_distinct 如果设置,临时表将在插入时消除重复项

这是有道理的,因为重复的记录被过滤掉了。源代码截图如下:

/*
  Create a temporary table to store the result of select_union.

  SYNOPSIS
    select_union::create_result_table()
      thd                thread handle
      column_types       a list of items used to define columns of the
                         temporary table
      is_union_distinct  if set, the temporary table will eliminate
                         duplicates on insert
      options            create options
      table_alias        name of the temporary table
      bit_fields_as_long convert bit fields to ulonglong

  DESCRIPTION
    Create a temporary table that is used to store the result of a UNION,
    derived table, or a materialized cursor.

  RETURN VALUE
    0                    The table has been created successfully.
    1                    create_tmp_table failed.
*/

bool
select_union::create_result_table(THD *thd_arg, List<Item> *column_types,
                                  bool is_union_distinct, ulonglong options,
                                  const char *table_alias,
                                  bool bit_fields_as_long, bool create_table)
{
  DBUG_ASSERT(table == 0);
  tmp_table_param.init();
  count_field_types(thd_arg->lex->current_select(), &tmp_table_param,
                    *column_types, false, true);
  tmp_table_param.skip_create_table= !create_table;
  tmp_table_param.bit_fields_as_long= bit_fields_as_long;

  if (! (table= create_tmp_table(thd_arg, &tmp_table_param, *column_types,
                                 (ORDER*) 0, is_union_distinct, 1,
                                 options, HA_POS_ERROR, (char*) table_alias)))
    return TRUE;
  if (create_table)
  {
    table->file->extra(HA_EXTRA_WRITE_CACHE);
    table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
  }
  return FALSE;

}
Run Code Online (Sandbox Code Playgroud)

有一个函数被调用create_tmp_table,因此MySQL总是在create_result_table调用该函数时创建一个临时表。

  • +1 表示`你会发现 EXPLAIN 输出有时会告诉你谎言`,这是 MySQL 宇宙中最伟大的概括性陈述。您应该为查询的拆分获得另一个 +1,以强制更好的查询行为。 (3认同)
  • @RolandoMySQLDBA 我回到这里是因为一个 [other](/sf/answers/4040907741/) 的回答,它记得我发布了这个......简而言之,解释也没有显示“来自需要日期到日期时间”并且需要查看更多记录,然后当您使用正确的数据类型时..但它在这里意味着离题,Oracle开发人员已经找到了这个答案并修复了它,因为它接缝解释给出了正确的信息["now"](https://www.db-fiddle.com/f/oK39GSt2CBiaWTqGVv9AA2/1) 也检查 5.7 和 8.. (2认同)