【问题标题】:Help: Optimize this query in MySQL帮助:在 MySQL 中优化此查询
【发布时间】:2011-03-18 06:34:06
【问题描述】:

这是我的表格,AUTO_INCREMENT 显示每个表格的大小:

tbl_clientes

CREATE TABLE `tbl_clientes` (
  `int_clientes_id_pk` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `str_clientes_documento` varchar(255) DEFAULT NULL,
  `str_clientes_nome_original` char(255) DEFAULT NULL,
  PRIMARY KEY (`int_clientes_id_pk`),
  UNIQUE KEY `str_clientes_documento` (`str_clientes_documento`),
  KEY `str_clientes_nome_original` (`str_clientes_nome_original`),
  KEY `nome_original_cliente_id` (`str_clientes_nome_original`,`int_clientes_id_pk`),
  KEY `cliente_id_nome_original` (`int_clientes_id_pk`,`str_clientes_nome_original`)
) ENGINE=MyISAM AUTO_INCREMENT=2815520 DEFAULT CHARSET=utf8

tbl_clienteEnderecos

CREATE TABLE `tbl_clienteEnderecos` (
  `int_clienteEnderecos_id_pk` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `int_clienteEnderecos_cliente_id_fk` bigint(20) unsigned NOT NULL,
  `str_clienteEnderecos_endereco` varchar(255) NOT NULL,
  `str_clienteEnderecos_cep` varchar(255) DEFAULT NULL,
  `str_clienteEnderecos_numero` varchar(255) DEFAULT NULL,
  `str_clienteEnderecos_complemento` varchar(255) DEFAULT NULL,
  `str_clienteEnderecos_bairro` varchar(255) DEFAULT NULL,
  `str_clienteEnderecos_cidade` varchar(255) DEFAULT NULL,
  `str_clienteEnderecos_uf` varchar(2) DEFAULT NULL,
  `int_clienteEnderecos_correspondencia` tinyint(1) NOT NULL DEFAULT '0',
  `int_clienteEnderecos_tipo` int(11) NOT NULL DEFAULT '1',
  PRIMARY KEY (`int_clienteEnderecos_id_pk`),
  KEY `int_clienteEnderecos_cliente_id_fk` (`int_clienteEnderecos_cliente_id_fk`),
  KEY `str_clienteEnderecos_cidade` (`str_clienteEnderecos_cidade`),
  KEY `str_clienteEnderecos_uf` (`str_clienteEnderecos_uf`),
  KEY `uf_cidade` (`str_clienteEnderecos_uf`,`str_clienteEnderecos_cidade`)
) ENGINE=MyISAM AUTO_INCREMENT=1542038 DEFAULT CHARSET=utf8

然后我运行这个查询来搜索,它会很快,正在使用索引:

EXPLAIN
SELECT * FROM tbl_clientes LEFT JOIN tbl_clienteEnderecos ON int_clienteEnderecos_cliente_id_fk = int_clientes_id_pk
GROUP BY str_clientes_nome_original, int_clientes_id_pk
ORDER BY str_clientes_nome_original, int_clientes_id_pk
LIMIT 0,20

EXPAIN 的结果是:

| id | select_type | table                | type  | possible_keys                      | key                                | key_len | ref                                               | rows | Extra |
+----+-------------+----------------------+-------+------------------------------------+------------------------------------+---------+---------------------------------------------------+------+-------+
|  1 | SIMPLE      | tbl_clientes         | index | NULL                               | nome_original_cliente_id           | 774     | NULL                                              |   20 |       |
|  1 | SIMPLE      | tbl_clienteEnderecos | ref   | int_clienteEnderecos_cliente_id_fk | int_clienteEnderecos_cliente_id_fk | 8       | mydb.tbl_clientes.int_clientes_id_pk |    1 |       |
+----+-------------+----------------------+-------+------------------------------------+------------------------------------+---------+---------------------------------------------------+------+-------+

好的,但我需要按 tbl_clienteEnderecos.str_clienteEnderecos_uf 进行过滤。它破坏了所有索引,使用临时表和文件排序(无索引)。这是查询:

EXPLAIN
SELECT * FROM tbl_clientes LEFT JOIN tbl_clienteEnderecos ON int_clienteEnderecos_cliente_id_fk = int_clientes_id_pk
WHERE str_clienteEnderecos_uf = "SP"
GROUP BY str_clientes_nome_original, int_clientes_id_pk
ORDER BY str_clientes_nome_original, int_clientes_id_pk
LIMIT 0,20

看,这是 EXPLAIN 的输出:

| id | select_type | table                | type   | possible_keys                                                        | key       | key_len | ref                                                                       | rows   | Extra                                        |
+----+-------------+----------------------+--------+----------------------------------------------------------------------+-----------+---------+---------------------------------------------------------------------------+--------+----------------------------------------------+
|  1 | SIMPLE      | tbl_clienteEnderecos | ref    | int_clienteEnderecos_cliente_id_fk,str_clienteEnderecos_uf,uf_cidade | uf_cidade | 9       | const                                                                     | 670654 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | tbl_clientes         | eq_ref | PRIMARY,cliente_id_nome_original                                     | PRIMARY   | 8       | mydb.tbl_clienteEnderecos.int_clienteEnderecos_cliente_id_fk |      1 |                                              |
+----+-------------+----------------------+--------+----------------------------------------------------------------------+-----------+---------+---------------------------------------------------------------------------+--------+----------------------------------------------+

用这个用where;使用临时的;使用文件排序它不会很快。我已经尝试了很多东西,如何优化这个查询?

是时候切换到 NoSQL/MongoDB 了吗?

【问题讨论】:

    标签: mysql optimization mongodb query-optimization


    【解决方案1】:

    如果索引不能帮助缩小结果范围,MySQL 通常不会使用索引。似乎“SP”出现在大约 670654 行中。由于这大约是总行数的 1/3,因此按磁盘顺序读取会更有效。

    您可以尝试使用 tbl_clienteEnderecos 的索引:

    KEY `test` (`str_clienteEnderecos_uf `, `int_clienteEnderecos_cliente_id_fk`)
    

    这可能足以让它使用索引。

    这两列有什么区别?它们看起来应该是一样的。

    int_clienteEnderecos_id_pk
    int_clienteEnderecos_cliente_id_fk
    

    编辑

    我了解列名的含义。我只是好奇这两个值是否应该相同。如果是这样,它将简化一些事情并将它们连接到表的主键上。我不确定所涉及的表的具体含义,所以我不知道它们之间是否存在1-1或1-0关系或一对多关系。

    我建议尝试只检索您想要的表的主键。例如,代替 select * try:

    EXPLAIN 
    SELECT int_clienteEnerecos_id_pk, int_clientes_id_pk 
    FROM tbl_clientes 
    LEFT JOIN tbl_clienteEnderecos ON int_clienteEnderecos_cliente_id_fk = int_clientes_id_pk
    WHERE str_clienteEnderecos_uf = "SP"
    GROUP BY str_clientes_nome_original, int_clientes_id_pk
    ORDER BY str_clientes_nome_original, int_clientes_id_pk
    LIMIT 0,20
    

    如果结果如我所愿,您可以在 Extra 列中看到“来自索引”。如果您需要返回其他字段,您可以进行另一次往返来获取它们,或者将它们添加到您的索引中。或者使用嵌套查询根据上面的查询结果来获取它们。

    另外,您为什么要按同一事物进行分组和排序?您是否期望外键有多个匹配项?

    【讨论】:

    • int_clienteEnderecos_id_pk 是 tbl_clienteEnderecos 表的主键。 int_clienteEnderecos_cliente_id_fk 是 tbl_clientes 表在 tbl_clienteEnderecos 表上的 FOREING KEY。
    • 我已经尝试过您的建议,使用此 KEY (str_clienteEnderecos_uf , int_clienteEnderecos_cliente_id_fk)。不工作,同样的结果:使用 where;使用临时的;使用文件排序。
    • 简短的回答是你的过滤器基本上没用。您没有为查询过滤掉足够的行来使用索引。在我上面的答案中添加另一个建议。
    • 嗨,我得到了同样缓慢的结果。 tbl_clientes 可以翻译为“customers”表,而 tbl_clienteEnderecos 可以翻译为“customer_addresses”。 int_clienteEnderecos_id_pk 是 id 字段,而 int_clienteEnderecos_cliente_id_fk 是外键:customer_id。
    • 很抱歉听到这个消息。在命名方面,这是我提出重复列问题的地方。如果每个 customer_addresses 行只有一个 customer_id,为什么不直接使用 customer_id 作为地址表的主键呢?如果可以有多个 customer_id 与地址关联,那么您应该将 customer_addresses 键作为外键放入客户表中。不知道如何帮助优化查询。
    【解决方案2】:

    我建议尝试以下方法;在这种情况下,子查询可能比连接更好地使用键。不过要小心;我不能在一堆 K & R 上发誓这个查询和你原来的一样。

    SELECT *,
           (SELECT *
                FROM tbl_clienteEnderecos
                WHERE int_clienteEnderecos_cliente_id_fk = int_clientes_id_pk AND
                      str_clienteEnderecos_uf = "SP") AS T2
        FROM tbl_clientes
        GROUP BY str_clientes_nome_original, int_clientes_id_pk
        HAVING T2.int_clienteEnderecos_id_pk IS NOT NULL
        ORDER BY str_clientes_nome_original, int_clientes_id_pk
        LIMIT 0, 20
    

    【讨论】:

    • 嗨,Brian,此查询不起作用:查询:SELECT *, (SELECT * FROM tbl_clienteEnderecos WHERE int_clienteEnderecos_cliente_id_fk = int_c... 错误代码:1241 操作数应包含 1 列。谢谢.
    • 我已经考虑过了,但无法对此查询进行任何修改,但无论如何都不会将其变成您正在尝试的内容。很抱歉占用了您的时间。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-06
    相关资源
    最近更新 更多