【问题标题】:Optimizing SQL query in MySQL在 MySQL 中优化 SQL 查询
【发布时间】:2018-03-14 20:56:35
【问题描述】:

我想知道这个查询为什么慢(大约10到20秒),使用的三个表有500,000条记录,这是查询:

SELECT  *, 'rg_egresos' AS nombre_tabla
    FROM  rg_detallexml DE
    INNER JOIN  rg_egresos EG
    INNER JOIN  rg_emisor EM  ON DE.idContador = EG.id
      AND  DE.idDetalleXml = EG.idDetalleXml
      AND  DE.idContador = EM.idContador
      AND  DE.idDetalleXml = EM.idDetalleXml
    WHERE  DE.idContador = '14894'
      AND  DATE_FORMAT(dateFechaHora, '%Y-%m-%d') BETWEEN '2017-10-01'
                                                      AND '2017-10-31'
      AND  strTipodeComprobante = 'egreso'
      AND  version_xml = '3.2'
      AND  estado_factura = 0
      AND  modificado = 0;

这就是我使用EXPLAIN时显示的内容

*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: EG
         type: index_merge
possible_keys: idx_idDetallexml,idx_estado_factura,idx_modificado,idx_idContador
          key: idx_idContador,idx_estado_factura,idx_modificado
      key_len: 4,4,4
          ref: NULL
         rows: 2111
        Extra: Using intersect(idx_idContador,idx_estado_factura,idx_modificado); Using where
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: DE
         type: eq_ref
possible_keys: PRIMARY,idx_strTipodeComprobante,idx_idContador,idx_version_xml
          key: PRIMARY
      key_len: 4
          ref: db_pwf.EG.idDetalleXml
         rows: 1
        Extra: Using where
*************************** 3. row ***************************
           id: 1
  select_type: SIMPLE
        table: EM
         type: ref
possible_keys: idx_idContador,idx_idDetallexml
          key: idx_idDetallexml
      key_len: 4
          ref: db_pwf.DE.idDetalleXml
         rows: 1
        Extra: Using where

您能找到改进查询的方法吗?我有其他查询处理更大的表,它们速度更快,所有必填字段都有索引,谢谢。

表 rg_detellexml:

+---------------------------------+--------------+------+-----+---------+----------------+
| Field                           | Type         | Null | Key | Default | Extra          |
+---------------------------------+--------------+------+-----+---------+----------------+
| idDetalleXml                    | int(10)      | NO   | PRI | NULL    | auto_increment |
| UUID                            | varchar(50)  | NO   | MUL | NULL    |                |
| dateFechaSubida                 | varchar(7)   | YES  |     | NULL    |                |
| idContador                      | int(10)      | NO   | MUL | NULL    |                |
| dateFechaHora                   | datetime     | YES  | MUL | NULL    |                |
| dateFechaHoraCertificacion      | datetime     | YES  |     | NULL    |                |
| dateFechaPago                   | datetime     | YES  |     | NULL    |                |
| intFolio                        | int(10)      | YES  |     | NULL    |                |
| strSerie                        | varchar(2)   | YES  |     | A       |                |
| doubleDescuento                 | double       | YES  |     | NULL    |                |
| doubleTotal                     | double       | YES  |     | NULL    |                |
| doubleSubtotal                  | double       | YES  |     | NULL    |                |
| duobleTotalImpuestosTrasladados | double       | YES  |     | NULL    |                |
| doubleTotalImpuestosRetenidos   | double       | YES  |     | NULL    |                |
| doubleTotalRetencionesLocales   | double       | YES  |     | NULL    |                |
| doubleTotalTrasladosLocales     | double       | YES  |     | NULL    |                |
| strTipodeComprobante            | varchar(15)  | YES  | MUL | NULL    |                |
| strMetodoDePago                 | varchar(150) | YES  |     | NULL    |                |
| strFormaDePago                  | varchar(150) | YES  |     | NULL    |                |
| strMoneda                       | varchar(10)  | YES  |     | NULL    |                |
| tipoCambio                      | double       | NO   |     | NULL    |                |
| strLugarExpedicion              | varchar(150) | YES  |     | NULL    |                |
| DIOT                            | int(1)       | YES  |     | 0       |                |
| version_xml                     | varchar(10)  | NO   | MUL | NULL    |                |
+---------------------------------+--------------+------+-----+---------+----------------+

表 rg_egresos:

+---------------------------+--------------+------+-----+---------+----------------+
| Field                     | Type         | Null | Key | Default | Extra          |
+---------------------------+--------------+------+-----+---------+----------------+
| id_egreso                 | int(11)      | NO   | PRI | NULL    | auto_increment |
| id                        | int(11)      | NO   | MUL | NULL    |                |
| idDetalleXml              | int(10)      | NO   | MUL | NULL    |                |
| idCatalogo                | int(19)      | NO   | MUL | NULL    |                |
| tipoCuenta                | int(11)      | NO   | MUL | NULL    |                |
| intRubro                  | int(1)       | NO   |     | NULL    |                |
| RFC                       | varchar(20)  | NO   | MUL | NULL    |                |
| compra_gastos_0_porciento | float        | NO   | MUL | NULL    |                |
| deducible                 | int(1)       | NO   |     | NULL    |                |
| compra_gastos_exentos     | float        | NO   |     | NULL    |                |
| no_deducibles             | float        | NO   |     | NULL    |                |
| estado_factura            | int(11)      | NO   | MUL | NULL    |                |
| fecha                     | date         | NO   | MUL | NULL    |                |
| total_xml                 | double       | NO   |     | NULL    |                |
| subtotal_xml              | double       | NO   |     | NULL    |                |
| iva_xml                   | double       | NO   |     | NULL    |                |
| total_impuestos           | double       | NO   |     | NULL    |                |
| abonado                   | double       | NO   |     | NULL    |                |
| subtotal                  | double       | NO   |     | NULL    |                |
| iva                       | double       | NO   |     | NULL    |                |
| pendiente                 | double       | NO   |     | NULL    |                |
| subtotal_sin_iva          | double       | NO   |     | NULL    |                |
| acreditable               | int(1)       | NO   | MUL | 0       |                |
| fecha_operacion           | datetime     | NO   | MUL | NULL    |                |
| modificado                | int(1)       | NO   | MUL | NULL    |                |
| UUID                      | varchar(50)  | NO   | MUL | NULL    |                |
| IEPS                      | double       | NO   |     | NULL    |                |
| retencion_iva             | double       | NO   |     | NULL    |                |
| retencion_isr             | double       | NO   |     | NULL    |                |
| imp_local                 | double       | NO   |     | 0       |                |
| enviado_a                 | int(11)      | NO   | MUL | NULL    |                |
| enviado_al_iva            | int(1)       | NO   |     | NULL    |                |
| EsNomina                  | int(1)       | NO   | MUL | 0       |                |
| dateFechaPago             | date         | NO   | MUL | NULL    |                |
| nota_credito              | int(1)       | NO   | MUL | NULL    |                |
| extranjero                | int(1)       | NO   | MUL | NULL    |                |
| pago_banco                | int(1)       | NO   | MUL | NULL    |                |
| idBanco_Pago              | int(20)      | NO   | MUL | NULL    |                |
| movimientoPago            | int(10)      | NO   |     | NULL    |                |
| saldo_banco               | varchar(50)  | NO   |     | NULL    |                |
| tipo_pago                 | int(1)       | NO   |     | 0       |                |
| responsable               | varchar(100) | NO   |     | NULL    |                |
+---------------------------+--------------+------+-----+---------+----------------+

表 rg_emisor:

+-----------------+--------------+------+-----+---------+----------------+
| Field           | Type         | Null | Key | Default | Extra          |
+-----------------+--------------+------+-----+---------+----------------+
| idEmisor        | int(10)      | NO   | PRI | NULL    | auto_increment |
| idDetalleXml    | int(10)      | NO   | MUL | NULL    |                |
| idContador      | int(10)      | NO   | MUL | NULL    |                |
| strRFC          | varchar(13)  | NO   |     | NULL    |                |
| strNombreEmisor | varchar(200) | YES  |     | NULL    |                |
| strRegimen      | varchar(250) | YES  |     | NULL    |                |
| strPais         | varchar(40)  | YES  |     | MX      |                |
| strEstado       | varchar(50)  | YES  |     | NULL    |                |
| intCP           | int(5)       | YES  |     | NULL    |                |
| strMunicipio    | varchar(250) | YES  |     | NULL    |                |
| strLocalidad    | varchar(250) | YES  |     | NULL    |                |
| strColonia      | varchar(250) | YES  |     | NULL    |                |
| intNumExt       | int(10)      | YES  |     | NULL    |                |
| intNumInt       | int(10)      | YES  |     | NULL    |                |
| strCalle        | varchar(250) | YES  |     | NULL    |                |
| regimenFiscal   | varchar(20)  | YES  |     | NULL    |                |
+-----------------+--------------+------+-----+---------+----------------+

【问题讨论】:

  • 首先,如果变量应该是某种类型然后强制转换它....例如日期变量..cast('2018-01-01' as date)
  • @NatiGetch 我正在使用 PHP,批处理?为什么?
  • 我应该对两个日期都使用 CAST 吗?
  • 此查询的行为是否符合您的预期?这里没有“on”子句,可能会导致大量交叉连接:FROM rg_detallexml DE INNER JOIN rg_egresos EG。呃...或阅读 scaisEdge 的答案
  • 是的,现在我正在使用 scalsEdge 的查询,但仍然没有区别。

标签: mysql sql performance


【解决方案1】:

现在您已经显示了表格,我们看到 rg_egresos.id 不是表格的 ID。因此,表中的一个 contador 可以有多个记录。让我们更仔细地看一下表和查询:

所有表都包含一个 contador ID 和一个 DetelleXml ID。你想在这两个领域加入他们。因此,您从 rg_detallexml 开始并获取 contador 的所有记录。找到idDetalleXml,然后搜索rg_egresosrg_emisors

这有点奇怪。首先,rg_detallexml 显然与一个 contador 相关联,但在其他表格中,rg_detallexml 可以链接到另一个 contador。好吧,这可能是可能的(可能是某种从/到关系)。但是有五个rg_egresos 记录和四个rg_emisors 记录用于rg_detallexml/contador,您将选择30 个记录,因为您将rg_egresos 记录与rg_emisors 记录组合在一起,这些记录并不真正相关。

无论如何:你想快速找到rg_detallexml

create index idx_de on rg_detallexml(idcontador, strtipodecomprobante, version_xml,
                                     datefechahora, iddetallexml);

那你找rg_egresos:

create index idx_eg on rg_egresos(id, iddetallexml, estado_factura, modificad);

最后你找rg_emisor:

create index idx_em on rg_emisor(idcontador, iddetallexml);

由于列存在于所有表中,我们当然可以按任何顺序浏览它们。从rg_detallexml 开始似乎也是最自然和最严格的,但这不一定是最好的。因此,您可能希望为 DBMS 提供另一个索引:

create index idx_eg2 on rg_egresos(id, estado_factura, modificad, iddetallexml);

这将允许 DBMS 首先在此表中查找 contador 的记录,并使用添加的条件在此处找到相关的 iddetallexml

【讨论】:

  • 我会试试这个,一个问题是要删除这些表的以前的索引?或者没关系?谢谢。
  • 索引只是提供给 DBMS。您可以拥有任意数量的。执行查询时,DBMS 将进行选择。更多的索引不会造成任何伤害(除了插入和删除由于更新索引的额外工作和消耗更多内存而略微减慢;出于这些原因,摆脱从未使用过的索引是个好主意) .
  • 我添加了这些新索引并且 EXPLAIN 输出发生了变化,但现在我不能告诉你它是否得到了改进,因为现在没有人使用系统我会在几个小时内检查它,我会告诉你如果效果更好,谢谢。
  • 我还有一个问题,我单独添加了我的旧索引,我不知道您可以添加多个具有相同索引名称的列,您如何确定哪些列可以一起在同一个索引中?标准是什么?谢谢。
  • 索引帮助查询找到记录。很多时候,表是通过它们的 ID 访问的,所以无论如何都有一个索引。但是还有其他标准,例如在您查找 contador 记录的示例中。为了帮助查询,您可以考虑浏览表格时最自然的顺序。我在另一个答案中首先尝试了这个,然后在这里再次做了这个。然后查看WHERE 子句和ON 子句,了解查找记录所需的内容。
【解决方案2】:

我看到的最大问题是在这部分:

DATE_FORMAT(dateFechaHora, '%Y-%m-%d') BETWEEN '2017-10-01' AND '2017-10-31'

dateFechaHora 是日期时间字段吗?为什么要将日期时间字段转换为字符串 (DATE_FORMAT)?即使你在 dateFechaHora 字段上有索引,它也不会被使用。

我建议您改用此代码:

and DateFechaHora >= '2017-10-01' and DateFechaHora < '2017-11-01'
                                                       ^^^^^^^^^^

是的,它是第二天,它不会被包括在内。

所以您的查询可能如下所示:

select
  *,
  'rg_egresos' AS nombre_tabla 
from
  rg_detallexml DE inner join rg_egresos EG
  on DE.idContador = EG.id and DE.idDetalleXml = EG.idDetalleXml 
  inner join rg_emisor EM on DE.idContador = EM.idContador 
  and DE.idDetalleXml = EM.idDetalleXml 
where
  DE.idContador = '14894' 
  and dateFechaHora >= '2017-10-01' and dateFechaHora < '2017-11-01' 
  and strTipodeComprobante = 'egreso'
  and version_xml = '3.2' 
  and estado_factura = 0 
  and modificado = 0
;

【讨论】:

  • 我尝试像 maSTAShuFu 说的那样使用 CAST,但还是一样,我会尝试你的答案,我会告诉你发生了什么。
  • 我试过使用这个查询,但还是一样,是的dateFechaHora 是一个日期时间字段
  • @ashe 一点改善都没有?定义了哪些索引?
  • 不,我可以给你看一下这个查询的解释输出,WHERE 子句中的所有字段都有索引,表的字段idDetalleXml, 'idContador`
  • 我添加了表格的结构。
【解决方案3】:

我在其他回复中看到了两个部分答案。让我们把它们绑在一起。

改变

  AND  DATE_FORMAT(dateFechaHora, '%Y-%m-%d') BETWEEN '2017-10-01'
                                                  AND '2017-10-31'

  AND DE.dateFechaHora >= '2017-10-01'
  AND DE.dateFechaHora  < '2017-10-01' + INTERVAL 1 MONTH

如果 DE 是一个好的起始表:

DE:  INDEX(idContador, strTipodeComprobante, version_xml, dateFechaHora)
       -- date last; others in any order

如果 EG 是一个更好的起始表:

EG:  INDEX(estado_factura, modificado, id)   -- in any order
DE:  INDEX(idContador, idDetalleXml,
           strTipodeComprobante, version_xml, dateFechaHora)

还有

EM:  INDEX(idContador, idDetalleXml)   -- in either order

“使用相交”几乎总是提示您应该使用复合索引而不是单独的索引。 (单独的索引可能对其他查询有用。)

(即添加所有这些索引,然后让优化器决定。)

请使用SHOW CREATE TABLE,而不是描述性较差的DESCRIBE

你真的需要SELECT *吗?

查询,根据我的建议:

SELECT  DE.*,
        EG.*,
        EM.*,
        'rg_egresos' AS nombre_tabla
    FROM  rg_detallexml DE
    INNER JOIN  rg_egresos EG
       ON  DE.idContador = EG.id
      AND  DE.idDetalleXml = EG.idDetalleXml
    INNER JOIN  rg_emisor EM
       ON  DE.idContador = EM.idContador
      AND  DE.idDetalleXml = EM.idDetalleXml
    WHERE  DE.idContador = '14894'
      AND  DE.dateFechaHora >= '2017-10-01'
      AND  DE.dateFechaHora  < '2017-10-01' + INTERVAL 1 MONTH
      AND  DE.strTipodeComprobante = 'egreso'
      AND  DE.version_xml = '3.2'
      AND  EG.estado_factura = 0
      AND  EG.modificado = 0;

【讨论】:

  • 我也会检查这个,所以当Using intersect出现时我需要考虑使用复合索引吗?
  • 是的。也许总是会更好。 More
  • 我试过了,确实更快,使用这样的日期范围更好吗?为什么比between更好?
  • @Ashe - 重新制定日期范围对性能没有影响。它提高了清晰度(显然你想要 1 个月)、简单性(不需要计算一个月的最后一天)、正确性(避免两端的午夜)等。速度来自更好的(“复合”)索引。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-16
  • 1970-01-01
  • 2011-01-22
  • 2011-07-07
  • 2018-12-21
相关资源
最近更新 更多