web-dev-qa-db-ja.com

不正な実行プランを選択するmysqlクエリ

間違った実行プランを選択しているため、永久に実行されているwhere条件として従属サブクエリを持つプライマリがあります。

主な質問:

SELECT  rtr.id AS id156_,
           rtr.procesada AS procesada156_,
           rtr.idTransaccion AS idTransa3_156_
FROM
   ReservasTiempoReal rtr 
        INNER JOIN Transacciones t ON t.id=rtr.idTransaccion
        INNER JOIN TransaccionLineas tl ON tl.idTransaccion = rtr.idTransaccion
        INNER JOIN Productos prod ON prod.id=tl.idProducto
        INNER JOIN Proveedores prov ON prov.id=prod.idProveedor
WHERE t.confirmada = 'S'
    AND t.codTipoTransaccion = 'RTRCT'
    AND t.fechaHora >= '2015-01-20 15:30:59'
    AND t.fechaHora <= '2015-01-21 15:30:59'
    AND prov.id=77        
       AND (EXISTS ( SELECT rtr2.id
                       FROM ReservasTiempoReal rtr2
                       INNER JOIN Transacciones t2 ON rtr2.idTransaccion = t2.id
                       WHERE t2.confirmada = 'S'
                                AND t2.codTipoTransaccion = 'ECTR'
                                AND t2.idTransaccionOriginal = rtr.idTransaccion));

そしてその実行計画:

enter image description here

赤で表示されているのは、「codTipoTransaccion」列と「confirmada」列を使用した使用済みインデックスです。最初のクエリ(内側のクエリなし)は10行のみを返します。しかし、トランザクションテーブル全体には500万を超えるレコードがあります。そしてそれらのほとんどは属性confirmada = ‘S’を持っています。インデックス「idxConfirmada」(「confirmada」フィールドによるインデックス)を削除すると、クエリの実行には10秒しかかからず、実行プランは次のようになります。

enter image description here

さらに情報を追加するために、プライマリクエリで識別されたレコードのみを使用して依存内部クエリを実行すると、6レコードのみが返されます。使用されたクエリは次のとおりです。

SELECT rtr2.id
FROM ReservasTiempoReal rtr2
      INNER JOIN Transacciones t2 ON rtr2.idTransaccion = t2.id
WHERE t2.confirmada = 'S'
      AND t2.codTipoTransaccion = 'ECTR'
      AND t2.idTransaccionOriginal IN (14748319,
14748519,14750327,14751060,14751126,14751351,14751354,14752902,14754752,
14754785)

「idxConfirmada」インデックスを削除したくないので、正しいプランを選択するためにORMが必要です。また、クエリが休止状態から来ているため、ヒントを使用してインデックスを無視することはできません。

(idxConfirmadaを使用せずに)正しいプランを選択するようにmysqlを作成するにはどうすればよいですか?

私はMYSQL5.1.51を使用しています。

テーブルの詳細を追加

CREATE TABLE `Transacciones` (
   `id` bigint(20) NOT NULL AUTO_INCREMENT,
   `fechaHora` datetime NOT NULL ,
   `nroTicket` varchar(255) NOT NULL ,
   `puntosGenerados` decimal(19,2) DEFAULT NULL,
   `creditoPendiente` decimal(19,2) DEFAULT NULL,
   `debitoPendiente` decimal(19,2) DEFAULT NULL,
   `saldoReal` decimal(19,2) DEFAULT NULL,
   `idTransaccionOriginal` bigint(20) DEFAULT NULL,
   `codTipoTransaccion` varchar(5) DEFAULT NULL,
   `confirmada` varchar(1) NOT NULL,
   `nroLote` varchar(5) DEFAULT NULL,
   `idMonedaImporteFinal` bigint(20) DEFAULT NULL,
   `totalImporteFinal` decimal(19,2) DEFAULT NULL,
   `anulada` varchar(1) DEFAULT NULL,
   `idLote` bigint(20) DEFAULT NULL,
   `online` varchar(1) DEFAULT NULL,
   `manualConPIN` varchar(1) DEFAULT NULL,
   `manualSinPIN` varchar(1) DEFAULT NULL,
   `facturada` varchar(1) DEFAULT NULL,
   PRIMARY KEY (`id`),
   KEY `FK2D546D5D771F9D3A` (`idTransaccionOriginal`),
   KEY `idxAnulada` (`anulada`),
   KEY `idxConfirmada` (`confirmada`),
   KEY `idxCodTipoTransaccion` (`codTipoTransaccion`),
   KEY `idxFechaHora_CodTipoTransaccion` (`fechaHora`,`codTipoTransaccion`),
   KEY `idxNroTicket` (`nroTicket`),
   KEY `idxNroLote` (`nroLote`),
   KEY `idxFechaHora` (`fechaHora`),
   KEY `idxOnline` (`online`),
   KEY `idxFacturada` (`facturada`),
   CONSTRAINT `FK2D546D5D771F9D3A` FOREIGN KEY (`idTransaccionOriginal`) REFERENCES `Transacciones` (`id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=15359563 DEFAULT CHARSET=latin1


CREATE TABLE `ReservasTiempoReal` (
   `id` bigint(20) NOT NULL AUTO_INCREMENT,
   `procesada` varchar(1) NOT NULL,
   `idTransaccion` bigint(20) NOT NULL,
   PRIMARY KEY (`id`),
   UNIQUE KEY `idTransaccion` (`idTransaccion`),
   KEY `FK2375B195C078E769` (`idTransaccion`),
   KEY `idxProcesada` (`procesada`),
   CONSTRAINT `FK2375B195C078E769` FOREIGN KEY (`idTransaccion`) REFERENCES `Transacciones` (`id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=50805 DEFAULT CHARSET=latin1
1
nerlijma

複合インデックスを使用します。

_INDEX(confirmada, codTipoTransaccion, idTransaccionOriginal, idTransaccion)
_

列は任意の順序にすることができるため、他のニーズを満たすため、および/または_t2_から他のインデックスを簡単に削除できるように列をシャッフルすることをお勧めします。

これについてさらに議論する必要がある場合は、各テーブルに_SHOW CREATE TABLE_を指定してください。

編集

For

_JOIN ...    ON rtr2.idTransaccion = t2.id
WHERE t2.confirmada = 'S'
  AND t2.codTipoTransaccion = 'ECTR'
  AND t2.idTransaccionOriginal IN (14748319, 14748519, ...)
_

これを持っている方がおそらく良いでしょう:

_INDEX(codTipoTransaccion, confirmada, idTransaccionOriginal)
_

idTransaccionOriginalは意図的に最後になります(INのため)。

これにより、オプティマイザーは_t2_で開始するようになります。 idTransaccionは他のテーブルのインデックスであることがわかります。したがって、JOINは効率的であるはずです。 (コールドキャッシュでは、まだ多くのI/Oが存在する可能性があります。)

オプティマイザー(ORMではない)が選択するための_CREATE TABLE_複数のあまり良くないインデックス。私が提案しているのは、オプティマイザーがすぐに選択できる「より良い」インデックスでなければなりません。オプティマイザーはすべてのインデックスを調べ、(指定されたクエリに対して)明らかに役に立たないインデックスを無視し、統計を調べます。状況によっては(おそらくあなたのものではない)、統計が貧弱で、INDEXの選択が貧弱になります。 1つのインデックスの「統計」は1つの数値です。バランスの取れた分布を前提としています(_confirmada='S'_では間違っていると指摘します)。

複合インデックスを使用することは(使用できる場合)、ほとんどの場合、インデックスを短くするよりも優れています。

(無関係)DROPこれらの2番目、冗長として:

_ KEY `idxFechaHora_CodTipoTransaccion` (`fechaHora`,`codTipoTransaccion`),
 KEY `idxFechaHora`                    (`fechaHora`),
_

また、私の提案したインデックスはKEY idxCodTipoTransaccion (codTipoTransaccion)を不要にします。

インデックス作成に関する詳細な説明

3
Rick James