【问题标题】:MySql very slow count on query with joinsMySql 对连接查询的计数非常慢
【发布时间】:2012-06-30 12:36:27
【问题描述】:

我对 mysql 有看法:

CREATE VIEW
    loggingquarantine_quarantine ( id, mail_id, partition_tag, content, rs, subject, sender, TIME,
    spam_level, size, sid, email ) AS
SELECT
    concat(CAST(`mr`.`mail_id` AS CHAR(255) charset utf8),CAST(`mr`.`partition_tag` AS CHAR(255)
    charset utf8))      AS `id`,
    `mr`.`mail_id`       AS `mail_id`,
    `mr`.`partition_tag` AS `partition_tag`,
    `mr`.`content`      AS `content`,
    `mr`.`rs`           AS `rs`,
    `m`.`subject`       AS `subject`,
    `m`.`from_addr`     AS `sender`,
    `m`.`time_num`      AS `TIME`,
    `m`.`spam_level`    AS `spam_level`,
    `m`.`size`          AS `size`,
    `m`.`sid`           AS `sid`,
    `maddr`.`email`          AS `email`
FROM
    (((`msgrcpt` `mr` JOIN `msgs` `m`
ON
    (
                `m`.`partition_tag` = `mr`.`partition_tag`
        AND
                `m`.`mail_id` = `mr`.`mail_id`
    )
)
JOIN `maddr` maddr
ON
    (
        `mr`.`rid` = `maddr`.`id`
    )
))

当我尝试计算此视图时,大约需要 13 分钟才能获得 250 万条记录。那慢得令人难以置信。所有字段都有索引。如果我确实计算每张桌子,它不会超过 20 秒。 这是mysql解释显示的内容:

mysql> explain SELECT COUNT(*) FROM `loggingquarantine_quarantine`;
+----+-------------+-------+--------+-----------------------------------------------------------------------+-------------------------+---------+-----------------------------------------------------------+---------+-------------+
| id | select_type | table | type   | possible_keys                                                         | key                     | key_len | ref                                                       | rows    | Extra       |
+----+-------------+-------+--------+-----------------------------------------------------------------------+-------------------------+---------+-----------------------------------------------------------+---------+-------------+
|  1 | SIMPLE      | maddr | index  | PRIMARY                                                               | maddr_partition_tag_idx | 5       | NULL                                                      | 1016497 | Using index |
|  1 | SIMPLE      | mr    | ref    | PRIMARY,msgrcpt_idx_rid,msgrcpt_mail_id_idx,msgrcpt_partition_tag_idx | msgrcpt_idx_rid         | 8       | mroute_logquar.maddr.id                                   |       2 | Using index |
|  1 | SIMPLE      | m     | eq_ref | PRIMARY,msgs_mail_id_idx,msgs_partition_tag_idx                       | PRIMARY                 | 22      | mroute_logquar.mr.partition_tag,mroute_logquar.mr.mail_id |       1 | Using index |
+----+-------------+-------+--------+-----------------------------------------------------------------------+-------------------------+---------+-----------------------------------------------------------+---------+-------------+

我怎样才能优化查询/视图,以便不需要 13 分钟来计算。当前查询有什么问题?

更新。 如果我直接在 select 上进行选择计数而不查看它仍然会得到相同的 14 分钟查询。

mysql> select count(1) FROM     (((`msgrcpt` `mr` JOIN `msgs` `m` ON     (                  `m`.`partition_tag` = `mr`.`partition_tag`         AND                 `m`.`mail_id` = `mr`.`mail_id`      ) ) JOIN `maddr` maddr ON     (         `mr`.`rid` = `maddr`.`id`     ) ));

+----------+
| count(1) |
+----------+
|  2582227 |
+----------+
1 row in set (14 min 28.96 sec)

这是我在三个单独的查询中进行计数时的结果:

mysql> select count(1) from msgrcpt;
+----------+
| count(1) |
+----------+
|  2587307 |
+----------+
1 row in set (46.02 sec)

mysql> select count(1) from msgs;
+----------+
| count(1) |
+----------+
|  2421710 |
+----------+
1 row in set (7.77 sec)

mysql> select count(1) from maddr;
+----------+
| count(1) |
+----------+
|   994880 |
+----------+
1 row in set (0.23 sec)

更新 2。

所有表都是 InnoDB。

mysql> SHOW status like 'key_%';                                                                                                                                                                               +------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| Key_blocks_not_flushed | 0     |
| Key_blocks_unused      | 26792 |
| Key_blocks_used        | 0     |
| Key_read_requests      | 0     |
| Key_reads              | 0     |
| Key_write_requests     | 0     |
| Key_writes             | 0     |
+------------------------+-------+

msgs 和 msgrcpt 表具有复合主键(对于 msgs,mail_id、partation_tag 和对于 msgrpt 的 (partition_tag,mail_id,rseqnum))。 更新 解释单表:

mysql> explain select count(1) from msgs;
+----+-------------+-------+-------+---------------+-------------------+---------+------+---------+-------------+
| id | select_type | table | type  | possible_keys | key               | key_len | ref  | rows    | Extra       |
+----+-------------+-------+-------+---------------+-------------------+---------+------+---------+-------------+
|  1 | SIMPLE      | msgs  | index | NULL          | msgs_idx_time_num | 4       | NULL | 2357360 | Using index |
+----+-------------+-------+-------+---------------+-------------------+---------+------+---------+-------------+
1 row in set (0.00 sec)

mysql> explain select count(1) from msgrcpt;
+----+-------------+---------+-------+---------------+----------------+---------+------+---------+-------------+
| id | select_type | table   | type  | possible_keys | key            | key_len | ref  | rows    | Extra       |
+----+-------------+---------+-------+---------------+----------------+---------+------+---------+-------------+
|  1 | SIMPLE      | msgrcpt | index | NULL          | msgrcpt_rs_idx | 3       | NULL | 2620758 | Using index |
+----+-------------+---------+-------+---------------+----------------+---------+------+---------+-------------+
1 row in set (0.00 sec)

mysql> explain select count(1) from maddr;
+----+-------------+-------+-------+---------------+-------------------------+---------+------+--------+-------------+
| id | select_type | table | type  | possible_keys | key                     | key_len | ref  | rows   | Extra       |
+----+-------------+-------+-------+---------------+-------------------------+---------+------+--------+-------------+
|  1 | SIMPLE      | maddr | index | NULL          | maddr_partition_tag_idx | 5       | NULL | 967058 | Using index |
+----+-------------+-------+-------+---------------+-------------------------+---------+------+--------+-------------+
1 row in set (0.00 sec)

更新。 为所有表创建表:

mysql> show create table msgrcpt;
| Table   | Create Table                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             
| msgrcpt | CREATE TABLE `msgrcpt` (
  `partition_tag` int(11) NOT NULL DEFAULT '0',
  `mail_id` varbinary(16) NOT NULL,
  `rseqnum` int(11) NOT NULL DEFAULT '0',
  `rid` bigint(20) unsigned NOT NULL,
  `is_local` char(1) NOT NULL DEFAULT '',
  `content` char(1) NOT NULL DEFAULT '',
  `ds` char(1) NOT NULL,
  `rs` char(1) NOT NULL,
  `bl` char(1) DEFAULT '',
  `wl` char(1) DEFAULT '',
  `bspam_level` float DEFAULT NULL,
  `smtp_resp` varchar(255) DEFAULT '',
  PRIMARY KEY (`partition_tag`,`mail_id`,`rseqnum`),
  KEY `msgrcpt_idx_rid` (`rid`),
  KEY `msgrcpt_mail_id_idx` (`mail_id`),
  KEY `msgrcpt_rs_idx` (`rs`),
  KEY `msgrcpt_ds_idx` (`ds`),
  KEY `msgrcpt_partition_tag_idx` (`partition_tag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |

| msgs  | CREATE TABLE `msgs` (
  `partition_tag` int(11) NOT NULL DEFAULT '0',
  `mail_id` varbinary(16) NOT NULL,
  `secret_id` varbinary(16) DEFAULT '',
  `am_id` varchar(20) NOT NULL,
  `time_num` int(10) unsigned NOT NULL,
  `time_iso` char(16) NOT NULL,
  `sid` bigint(20) unsigned NOT NULL,
  `policy` varchar(255) DEFAULT '',
  `client_addr` varchar(255) DEFAULT '',
  `size` int(10) unsigned NOT NULL,
  `originating` char(1) NOT NULL DEFAULT '',
  `content` char(1) DEFAULT NULL,
  `quar_type` char(1) DEFAULT NULL,
  `quar_loc` varbinary(255) DEFAULT '',
  `dsn_sent` char(1) DEFAULT NULL,
  `spam_level` float DEFAULT NULL,
  `message_id` varchar(255) DEFAULT '',
  `from_addr` varchar(255) DEFAULT '',
  `subject` varchar(255) DEFAULT '',
  `host` varchar(255) NOT NULL,
  PRIMARY KEY (`partition_tag`,`mail_id`),
  KEY `msgs_idx_sid` (`sid`),
  KEY `msgs_idx_mess_id` (`message_id`),
  KEY `msgs_idx_time_num` (`time_num`),
  KEY `msgs_mail_id_idx` (`mail_id`),
  KEY `msgs_partition_tag_idx` (`partition_tag`),
  KEY `msgs_content_idx` (`content`),
  FULLTEXT KEY `ft_from_addr` (`from_addr`),
  FULLTEXT KEY `ft_subject` (`subject`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |

| maddr | CREATE TABLE `maddr` (
  `partition_tag` int(11) DEFAULT '0',
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `email` varbinary(255) NOT NULL,
  `domain` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `part_email` (`partition_tag`,`email`),
  KEY `maddr_email_idx` (`email`),
  KEY `maddr_partition_tag_idx` (`partition_tag`)
) ENGINE=InnoDB AUTO_INCREMENT=3373444 DEFAULT CHARSET=utf8 |

此查询与配置文件:

mysql> SET PROFILING=1; SELECT
Query OK, 0 rows affected (0.00 sec)

    -> count(1)
    -> FROM
    ->     (((`msgrcpt` `mr` JOIN `msgs` `m`
    -> ON
    ->     (
    ->                 `m`.`partition_tag` = `mr`.`partition_tag`
    ->         AND
    ->                 `m`.`mail_id` = `mr`.`mail_id`
    ->     )
    -> )
    -> JOIN `maddr` maddr
    -> ON
    ->     (
    ->         `mr`.`rid` = `maddr`.`id`
    ->     )
    -> )); SHOW PROFILE ALL;

+----------+
| count(1) |
+----------+
|  4279394 |
+----------+
1 row in set (23 min 56.61 sec)

+----------------------+------------+-----------+------------+-------------------+---------------------+--------------+---------------+---------------+-------------------+-------------------+-------------------+-------+-----------------------+------------------+-------------+
| Status               | Duration   | CPU_user  | CPU_system | Context_voluntary | Context_involuntary | Block_ops_in | Block_ops_out | Messages_sent | Messages_received | Page_faults_major | Page_faults_minor | Swaps | Source_function       | Source_file      | Source_line |
+----------------------+------------+-----------+------------+-------------------+---------------------+--------------+---------------+---------------+-------------------+-------------------+-------------------+-------+-----------------------+------------------+-------------+
| starting             |   0.000161 |  0.000000 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | NULL                  | NULL             |        NULL |
| checking permissions |   0.000030 |  0.000000 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 1 |     0 | check_access          | sql_parse.cc     |        5043 |
| checking permissions |   0.000019 |  0.000000 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | check_access          | sql_parse.cc     |        5043 |
| checking permissions |   0.000020 |  0.000000 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | check_access          | sql_parse.cc     |        5043 |
| Opening tables       |   0.000039 |  0.000000 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | open_tables           | sql_base.cc      |        5014 |
| System lock          |   0.000026 |  0.000000 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | mysql_lock_tables     | lock.cc          |         304 |
| init                 |   0.000040 |  0.000000 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | mysql_select          | sql_select.cc    |        1041 |
| optimizing           |   0.000030 |  0.000000 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | optimize              | sql_optimizer.cc |         138 |
| statistics           |   0.000063 |  0.000000 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | optimize              | sql_optimizer.cc |         358 |
| preparing            |   0.000032 |  0.000000 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | optimize              | sql_optimizer.cc |         470 |
| executing            |   0.000021 |  0.000000 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | exec                  | sql_executor.cc  |         137 |
| Sending data         | 999.999999 | 97.014251 |  10.376423 |            681167 |               25822 |      5157072 |       1951032 |             0 |                 0 |                 4 |               277 |     0 | execute               | sql_executor.cc  |         758 |
| end                  |   0.000106 |  0.000000 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | mysql_select          | sql_select.cc    |        1071 |
| query end            |   0.000017 |  0.000000 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | mysql_execute_command | sql_parse.cc     |        4761 |
| closing tables       |   0.000021 |  0.000000 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | mysql_execute_command | sql_parse.cc     |        4809 |
| freeing items        |   0.000030 |  0.000000 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | mysql_parse           | sql_parse.cc     |        5997 |
| logging slow query   |   0.000059 |  0.000000 |   0.000000 |                 0 |                   0 |            0 |             8 |             0 |                 0 |                 0 |                 0 |     0 | log_slow_statement    | sql_parse.cc     |        1720 |
| cleaning up          |   0.000019 |  0.000000 |   0.000000 |                 0 |                   0 |            0 |             0 |             0 |                 0 |                 0 |                 0 |     0 | dispatch_command      | sql_parse.cc     |        1654 |
+----------------------+------------+-----------+------------+-------------------+---------------------+--------------+---------------+---------------+-------------------+-------------------+-------------------+-------+-----------------------+------------------+-------------+
18 rows in set (0.02 sec)

表上的索引:

mysql> show index from msgs;
+-------+------------+------------------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name               | Seq_in_index | Column_name   | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+------------------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| msgs  |          0 | PRIMARY                |            1 | partition_tag | A         |          16 |     NULL | NULL   |      | BTREE      |         |               |
| msgs  |          0 | PRIMARY                |            2 | mail_id       | A         |     4174440 |     NULL | NULL   |      | BTREE      |         |               |
| msgs  |          1 | msgs_idx_sid           |            1 | sid           | A         |     2087220 |     NULL | NULL   |      | BTREE      |         |               |
| msgs  |          1 | msgs_idx_mess_id       |            1 | message_id    | A         |     4174440 |     NULL | NULL   | YES  | BTREE      |         |               |
| msgs  |          1 | msgs_idx_time_num      |            1 | time_num      | A         |     1391480 |     NULL | NULL   |      | BTREE      |         |               |
| msgs  |          1 | msgs_mail_id_idx       |            1 | mail_id       | A         |     4174440 |     NULL | NULL   |      | BTREE      |         |               |
| msgs  |          1 | msgs_partition_tag_idx |            1 | partition_tag | A         |          16 |     NULL | NULL   |      | BTREE      |         |               |
| msgs  |          1 | msgs_content_idx       |            1 | content       | A         |          16 |     NULL | NULL   | YES  | BTREE      |         |               |
| msgs  |          1 | ft_from_addr           |            1 | from_addr     | NULL      |     4174440 |     NULL | NULL   | YES  | FULLTEXT   |         |               |
| msgs  |          1 | ft_subject             |            1 | subject       | NULL      |     4174440 |     NULL | NULL   | YES  | FULLTEXT   |         |               |
+-------+------------+------------------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
10 rows in set (0.97 sec)

MSGRCPT

mysql> show index from msgrcpt;
+---------+------------+---------------------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table   | Non_unique | Key_name                  | Seq_in_index | Column_name   | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+---------+------------+---------------------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| msgrcpt |          0 | PRIMARY                   |            1 | partition_tag | A         |          29 |     NULL | NULL   |      | BTREE      |         |               |
| msgrcpt |          0 | PRIMARY                   |            2 | mail_id       | A         |     5218535 |     NULL | NULL   |      | BTREE      |         |               |
| msgrcpt |          0 | PRIMARY                   |            3 | rseqnum       | A         |     5218535 |     NULL | NULL   |      | BTREE      |         |               |
| msgrcpt |          1 | msgrcpt_idx_rid           |            1 | rid           | A         |      347902 |     NULL | NULL   |      | BTREE      |         |               |
| msgrcpt |          1 | msgrcpt_mail_id_idx       |            1 | mail_id       | A         |     5218535 |     NULL | NULL   |      | BTREE      |         |               |
| msgrcpt |          1 | msgrcpt_rs_idx            |            1 | rs            | A         |          29 |     NULL | NULL   |      | BTREE      |         |               |
| msgrcpt |          1 | msgrcpt_ds_idx            |            1 | ds            | A         |          29 |     NULL | NULL   |      | BTREE      |         |               |
| msgrcpt |          1 | msgrcpt_partition_tag_idx |            1 | partition_tag | A         |          29 |     NULL | NULL   |      | BTREE      |         |               |
+---------+------------+---------------------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
8 rows in set (0.70 sec)

MADDR:

mysql> show index from maddr;
+-------+------------+-------------------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name                | Seq_in_index | Column_name   | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+-------------------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| maddr |          0 | PRIMARY                 |            1 | id            | A         |     1653970 |     NULL | NULL   |      | BTREE      |         |               |
| maddr |          0 | part_email              |            1 | partition_tag | A         |          19 |     NULL | NULL   | YES  | BTREE      |         |               |
| maddr |          0 | part_email              |            2 | email         | A         |     1653970 |     NULL | NULL   |      | BTREE      |         |               |
| maddr |          1 | maddr_email_idx         |            1 | email         | A         |     1653970 |     NULL | NULL   |      | BTREE      |         |               |
| maddr |          1 | maddr_partition_tag_idx |            1 | partition_tag | A         |          19 |     NULL | NULL   | YES  | BTREE      |         |               |
+-------+------------+-------------------------+--------------+---------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
5 rows in set (0.41 sec)

Inno 数据库缓冲区大小

mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
+-------------------------+------------+
| Variable_name           | Value      |
+-------------------------+------------+
| innodb_buffer_pool_size | 2147483648 |
+-------------------------+------------+
1 row in set (0.02 sec)

【问题讨论】:

  • 如果你只是在 SELECT 声明(而不是视图)上执行 COUNT 需要多长时间?
  • select count(1) from msgrcpt;select count(1) from msgs; 不应该那么长,即使没有索引。运行 SHOW status like 'key_%'; 并检查 Key_blocks_unusedKey_reads 并根据需要增加 MySQL key_buffer 并重试。
  • 完成所有相关表的CREATE TABLE 语句会很不错。
  • 1.请提供以下输出:SET PROFILING=1; [run your query here]; SHOW PROFILE ALL;。 2. 请运行OPTIMIZE TABLES msgs, msgrcpt, maddr;(警告,这会锁定您的表非常很长时间,可能是几个小时)。 3.(出于好奇)请再次提供上述步骤 1 的输出。 4.请提供SHOW VARIABLES LIKE ('innodb_buffer_pool_size');的输出。
  • 服务器本身出了点问题。在 46 秒内对 2M 行表执行 SELECT COUNT(*) 太慢了。你能告诉我们更多关于你使用的服务器环境吗?操作系统和 MySQL 版本?你在使用虚拟实例吗?您使用网络附加磁盘吗?这台服务器的负载如何?您能否提供 iostat -dx 5 3 和 vmstat 5 3 的输出?

标签: mysql sql query-optimization


【解决方案1】:

您是否尝试过使用一些 STRAIGHT_JOIN 而不是简单的 JOIN 来改变 JOIN 中使用的表的顺序?有时查询优化器不会为特定查询选择理想的顺序。换句话说,你看到了什么样的表现?

SELECT
  concat(CAST(`mr`.`mail_id` AS CHAR(255) charset utf8),CAST(`mr`.`partition_tag` AS CHAR(255) charset utf8)) AS `id`,
  `mr`.`mail_id`       AS `mail_id`,
  `mr`.`partition_tag` AS `partition_tag`,
  `mr`.`content`      AS `content`,
  `mr`.`rs`           AS `rs`,
  `m`.`subject`       AS `subject`,
  `m`.`from_addr`     AS `sender`,
  `m`.`time_num`      AS `TIME`,
  `m`.`spam_level`    AS `spam_level`,
  `m`.`size`          AS `size`,
  `m`.`sid`           AS `sid`,
  `maddr`.`email`          AS `email`
FROM
  `msgrcpt` `mr`
    STRAIGHT_JOIN `msgs` `m`
      ON
        `m`.`partition_tag` = `mr`.`partition_tag` AND
        `m`.`mail_id` = `mr`.`mail_id`
    STRAIGHT_JOIN `maddr` maddr
      ON
        `mr`.`rid` = `maddr`.`id`

或者由于您在 msgrcpt 表上使用了一些重型转换和强制转换,如果您像这样在查询中强制它最后加入会发生什么?

SELECT
  concat(CAST(`mr`.`mail_id` AS CHAR(255) charset utf8),CAST(`mr`.`partition_tag` AS CHAR(255) charset utf8)) AS `id`,
  `mr`.`mail_id`       AS `mail_id`,
  `mr`.`partition_tag` AS `partition_tag`,
  `mr`.`content`      AS `content`,
  `mr`.`rs`           AS `rs`,
  `m`.`subject`       AS `subject`,
  `m`.`from_addr`     AS `sender`,
  `m`.`time_num`      AS `TIME`,
  `m`.`spam_level`    AS `spam_level`,
  `m`.`size`          AS `size`,
  `m`.`sid`           AS `sid`,
  `maddr`.`email`          AS `email`
FROM
  `maddr` `maddr`
    STRAIGHT_JOIN `msgrcpt` `mr`
      ON
        `maddr`.`id` = `mr.`rid`
    STRAIGHT_JOIN `msgs` `m`
      ON
        `m`.`partition_tag` = `mr`.`partition_tag` AND
        `m`.`mail_id` = `mr`.`mail_id`

如果您愿意,您可以更多地使用订单,可能有一个“魔术订单”会导致您的查询压缩。例如,另一个变体可能是:

FROM
  `msgs` `m`
    STRAIGHT_JOIN `msgrcpt` `mr`
      ON
        `m`.`partition_tag` = `mr`.`partition_tag` AND
        `m`.`mail_id` = `mr`.`mail_id`
    STRAIGHT_JOIN `maddr`
      ON
        `maddr`.`id` = `mr`.`rid`

...等等。

此外,在您的 SELECT 列中,所有这些转换势必会降低您的性能,因为无论您选择多少行,它都必须这样做,这可能很多。您是否考虑过只在已计算的 msgrcpt 表中添加一列,以便不必在查询中执行此操作?如果您不想在代码中考虑额外的列,您可以向数据库添加触发器以使用计算值有效地自动更新它。通常,使用这种优化有点矫枉过正,但在对数百万行执行 SELECT 查询时,它可能会起到作用。

编辑:这是我要更改您的 msgrcpt 表的建议。由于它正在运行触发器来计算新列值,因此它会使插入行的速度变慢一点,但我认为它会在加快您尝试运行的查询方面得到回报。标准免责声明适用 -请勿在未先在测试副本上彻底测试的情况下在您的生产数据库上运行它!

-- The UPDATE command will probably take some time to run since it's updating
-- millions of rows. Be patient!
ALTER TABLE `msgrcpt` ADD COLUMN `friendly_id` TEXT NULL AFTER `rs`;
UPDATE `msgrcpt` SET
  `friendly_id` = CONCAT(CAST(`mail_id` AS CHAR(255) CHARSET utf8),
    CAST(`partition_tag` AS CHAR(255) CHARSET utf8));
DELIMITER $$
CREATE TRIGGER `trig_calc_id` BEFORE INSERT ON `msgrcpt`
  FOR EACH ROW BEGIN
    SET NEW.`friendly_id` =
      CONCAT(CAST(NEW.`mail_id` AS CHAR(255) CHARSET utf8),
        CAST(NEW.`partition_tag` AS CHAR(255) CHARSET utf8));
  END $$
CREATE TRIGGER `trig_update_id` BEFORE UPDATE ON `msgrcpt`
  FOR EACH ROW BEGIN
    SET NEW.`friendly_id` =
      CONCAT(CAST(NEW.`mail_id` AS CHAR(255) CHARSET utf8),
        CAST(NEW.`partition_tag` AS CHAR(255) CHARSET utf8));
  END $$
DELIMITER ;

现在不要选择那些乱七八糟的 CONCATed CASTed 东西,只需选择 mr.friendly_id (或您选择的任何名称列)。你的表现应该会好很多。

希望这会有所帮助,并告诉我进展如何!

【讨论】:

    【解决方案2】:

    several methods 数据库如何执行表连接。如section on EXPLAIN output 中所述,MySQL 仅使用Nested-loops 加入。嵌套循环的工作方式是理解为什么这个查询需要这么多时间的关键。

    根据您的EXPLAIN 输出,MySQL 首先扫描maddr 表,因此您有一个1 016 497 执行循环。在每个执行数据库内部,对一个小表执行两次索引扫描。即使成本:

    • 开始新的索引扫描;
    • 在索引中搜索条目;
    • 完成扫描

    真的很小(因为数据很可能会被缓存那么多次执行),无论如何都需要几毫秒。然后乘以2,因为msgrcptmsgs 都在内部扫描。然后乘以外部循环的执行次数,这给了你相当长的时间。

    您的视图定义对返回的数据没有过滤器,因此必须扫描和连接所有表。鉴于嵌套循环的工作方式,很明显,当外部查询包含最少数量的循环时,变体是性能最好的变体。考虑到所有表的大小,索引扫描(根据EXPLAIN 输出在索引上扫描所有 3 个表)将检查:

    • msgrcpt:日志2(2587307) + 1 = 22 页,
    • msgs: 日志2(2421710) + 1 = 22 页和
    • maddr: log2(994880) + 1 = 每次扫描 21 页

    最坏的情况情况下。这表明即使使用索引扫描,表扫描时间也是相当的并且非常重要。不过,maddr 更小,这就是 MySQL 选择先扫描它的原因。

    在我看来,数据库选择的计划是给定情况下最好的计划。而且 MySQL 仅支持嵌套循环连接这一事实没有提供优化的可能性,因为外部循环已经是最小的循环。 Hash join 可能已在此处使用以获得更好的性能。


    那么,如何让事情在这种情况下发挥作用呢?

    我建议只使用视图来查询带有过滤器的数据,这将使 MySQL 有更多机会优化查询并快速返回数据。

    为了获得行数,我会进行估计(如果可以接受的话),为每个单独的表计算 count(*) 并取最大的一个。

    另一种方法是重新设计架构,使其中一个表成为主表,以便视图中的行数应始终与主表中的行数匹配。我不确定如何使用给定的设计来做到这一点,抱歉。

    如果没有一个选项是可接受的,很遗憾,我看不到其他优化此查询的方法。

    【讨论】:

      【解决方案3】:

      我同意King Skippus 关于两个元素的观点……STRAIGHT_JOIN 有助于推翻优化器执行计划……以及为您保留预聚合的触发器。但是,如果您想应用 WHERE 子句来限制,那您就完蛋了。

      此外,如果您只关心一个 COUNT,那么最好将 vyegorov 注释丢弃所有列。通过让所有其他列强制从所有实际数据页中提取数据。

      我会将我的查询呈现为

      SELECT STRAIGHT_JOIN COUNT(*)
         FROM
            msgrcpt mr
               JOIN msgs m
                  ON mr.mail_id = m.mail_id
                 AND mr.partition_tag = m.partition_tag
               JOIN maddr
                  ON mr.rid = maddr.id
      

      我知道你提到了索引,但我会 MsgRcpt 表索引通过 (mail_id, partition_tag) 通过相同的消息表索引(mail_id,partition_tag) (id) 上的 MAddr 索引

      现在,为什么在索引上采用一种方式与另一种方式?我会尝试根据第一个索引字段中的最小聚合来做。不确切知道您的卷基础是什么(邮件 id、分区标签),但可以对客户订单系统进行抽样。您可能在一天内有 10,000 人订购某样东西,但对于给定的人来说,在给定的一天会有一两个订单。首先找到那个人和那个单独的日期而不是一天然后找到一个人会更容易。这可能会对尝试首先通过 LARGE 列表,然后是 small vs small/small 的查询性能产生影响。

      只做一个 COUNT(*),你就得到了一个计数。不需要原始表中的其他列,并且正在加载每个页面,因为它只需要索引组件来限定结果中的记录。

      如果您确实想要所有数据,那么我会执行上述相同的索引注意事项,但将查询设置为...

      CREATE VIEW
          loggingquarantine_quarantine ( id, mail_id, partition_tag, content, rs, subject, sender, TIME,
          spam_level, size, sid, email ) AS
      
      SELECT STRAIGHT_JOIN
            concat(CAST(mr.mail_id AS CHAR(255) charset utf8), 
                   CAST(mr.partition_tag AS CHAR(255) charset utf8)) AS `id`,
            mr.mail_id,
            mr.partition_tag,
            mr.content,
            mr.rs,
            m.subject,
            m.from_addr AS sender,
            m.time_num AS `TIME`,
            m.spam_level,
            m.size,
            m.sid,
            maddr.email
         FROM
            msgrcpt mr
               JOIN msgs m
                  ON mr.mail_id = m.mail_id
                 AND mr.partition_tag = m.partition_tag
               JOIN maddr
                  ON mr.rid = maddr.id
      

      【讨论】:

        【解决方案4】:

        视图查询本身以及代码中视图的使用可能会影响性能。欲了解更多信息,请阅读文章 - MySQL VIEW as performance troublemaker

        解决方法:

        • 执行简单查询,不要使用视图。
        • 如果您不想在另一个 SELECT 语句中将此视图用作数据集,那么您可以尝试创建存储过程而不是视图。

        【讨论】:

        • 您是否在没有视图和没有 COUNT 函数的情况下测试了该查询? COUNT 函数可能需要很长时间。
        • 我不认为选择所有行是一个好主意。当我在 mysql 控制台执行此操作时,mysql 消失了:)
        • 控制台尝试读取所有记录。所以,它可能会因为 fetch 而冻结;-)
        【解决方案5】:

        尝试使用 COUNT(*) insted COUNT(1) 因为 MySQL 优化器有很多错误 http://www.mysqlperformanceblog.com/2007/04/10/count-vs-countcol/

        【讨论】:

          【解决方案6】:

          对不起:我看到您的 maddr 表有一个 partition_tag id,但在构成 VIEW 的最后一个 JOIN 中没有使用该分区标签。这是应该的吗?

          无论如何,在我看来,您的查询可能会受益于 maddr 上同时包含 id 和电子邮件的索引。这应该允许直接从索引中提取具有较少 I/O 的电子邮件(评估各种索引的性能可能会有所帮助 - 在我看来,例如,单独使用电子邮件字段的索引很少使用,但它取决于您知道而我不知道的工作流程,所以我很可能会弄错。

          【讨论】:

            【解决方案7】:

            好的 :),所以一方面你说 - 我们有 250 万个条目,另一方面(根据你的说法)计数返回 4279394(略高:))。可能的原因(对不起,几乎是凌晨 3 点 :))是 - 您没有指定 msgrcpt 的完整主键(缺少 rseqnum),因此如果优化器选择 maddr 或 msgs 作为主表 - 您最终可能会得到相同的结果msgrcpt 的条目。同样基于 msgrcpt 和 msgs 的计数时间差异,我强烈建议对 msgrcpt 进行碎片整理。我相信在对直接连接进行碎片整理和使用(已经推荐)之后,您的问题将消失(如果不是 - 也许您可以找到一种即时计算的方法 - 无论您需要为 rseqnum / spam_level 计算什么,或者,如果 spam_level 相同完整的消息 - 也许您可以在一开始就通过提供 rseqnum 的硬编码值来限制选择。

            【讨论】:

              【解决方案8】:

              加入需要 BIG mysql 负载。 以我的经验, 创建临时表并从中选择数据, 提高数据库查询速度。

              这是我的代码,代码 2 显示 100 倍的速度。 您可以更改查询代码,如代码 2 样式。 对于大型复杂的数据库查询,使用临时表提高数据库查询速度。

              1. 制作临时表使查询更简单,减少mysql db负载。
              2. 从临时表中选择数据也需要较少的数据库负载。

              代码 1

                  $sql = " select distinct wr_parent from $write_table where $sql_search ";
                  $result = sql_query($sql);
                  $total_count = mysql_num_rows($result);
              

              代码 2

                  $sql = " select wr_parent from $write_table where $sql_search ";
                  $sql_tmp = " create TEMPORARY table list_tmp_count as $sql ";
                  $sql_ord = " select distinct wr_parent from list_tmp_count ";
              
                  @mysql_query($sql_tmp) or die("<p>$sql_tmp<p>" . mysql_errno() . " : " .  mysql_error() . "<p>error file : $_SERVER[PHP_SELF]");
                  $result = @mysql_query($sql_ord) or die("<p>$sql_ord<p>" . mysql_errno() . " : " .  mysql_error() . "<p>error file : $_SERVER[PHP_SELF]");
                  $total_count = mysql_num_rows($result);
              

              mysql temporary table

              如果你临时表的位置在 tempfs(内存区域)速度也翻了一番。

              【讨论】:

                【解决方案9】:

                count()中写一个主键列,让它更快。like count(id)

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2022-10-01
                  • 2022-01-12
                  • 1970-01-01
                  • 2012-10-23
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多