inner join
を作成するときにMySQLがインデックスを使用しない理由に関する情報を取得しようとしていて、最後にORDER BY
を試行しています。
ここにSQLクエリがあります:
SELECT
*
FROM
product p INNER JOIN productStore ps ON p.productUUID = ps.productUUID
ORDER BY
ps.storeTitle
LIMIT 50;
この選択による順序を使用している場合、3,5秒以上かかります。同じSQLを実行するために、1.6msのようにして順序を削除すると、説明SQLは次のようになります。
ORDER BY
の場合:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE ps ALL PRIMARY NULL NULL NULL 942187 Using filesort
1 SIMPLE p eq_ref PRIMARY PRIMARY 16 foeniks_core.ps.productUUID 1 NULL
ORDER BY
なし:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE ps ALL PRIMARY NULL NULL NULL 942187 NULL
1 SIMPLE p eq_ref PRIMARY PRIMARY 16 foeniks_core.ps.productUUID 1 NULL
インデックス作成権がないフィールドは、長さが282のvarcharです。
私のテーブルデザインはここにあります:
CREATE TABLE `productStore` (
`productUUID` binary(16) NOT NULL,
`storeUUID` binary(16) NOT NULL,
`distributorLastUsed` binary(16) DEFAULT NULL,
`storeTitle` varchar(282) DEFAULT NULL,
`storeUrl` varchar(282) DEFAULT NULL,
`storeDescription` text,
`storeDescriptionDemo` text,
`storePrice` int(11) NOT NULL DEFAULT '0',
`storePriceNext` int(11) NOT NULL DEFAULT '0',
`storePriceCost` int(11) NOT NULL DEFAULT '0',
`overwrites` int(11) NOT NULL DEFAULT '0',
`updated` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`added` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`allowDisplay` tinyint(1) NOT NULL DEFAULT '0',
`activated` tinyint(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`productUUID`,`storeUUID`),
KEY `productStoreLanguageToStore_idx` (`storeUUID`),
KEY `productStoreToDistributor_idx` (`distributorLastUsed`),
KEY `storeUrl` (`storeUrl`(180)) USING BTREE,
KEY `testStoreTitle` (`storeTitle`(182)),
CONSTRAINT `productStoreToDistributor` FOREIGN KEY (`distributorLastUsed`) REFERENCES `distributor` (`distributorUUID`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `productStoreToProduct` FOREIGN KEY (`productUUID`) REFERENCES `product` (`productUUID`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `productStoreToStore` FOREIGN KEY (`storeUUID`) REFERENCES `store` (`storeUUID`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
製品表:
CREATE TABLE `product` (
`productUUID` binary(16) NOT NULL,
`productManufacturerUUID` binary(16) NOT NULL,
`productManufacturerSKU` varchar(40) DEFAULT NULL,
`productEan` varchar(40) DEFAULT NULL,
`cnetID` varchar(10) DEFAULT NULL,
`edbID` int(10) DEFAULT NULL,
`overwrites` int(10) NOT NULL DEFAULT '0',
`updated` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`added` datetime NOT NULL DEFAULT '1000-01-01 00:00:00',
`activated` tinyint(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`productUUID`),
KEY `manufacturerSKU` (`productManufacturerSKU`(16)),
KEY `productToManufacturer_idx` (`productManufacturerUUID`),
KEY `cnetID` (`cnetID`),
KEY `productEAN` (`productEan`),
CONSTRAINT `productToManufacturer` FOREIGN KEY (`productManufacturerUUID`) REFERENCES `manufacturer` (`manufacturerUUID`) ON DELETE NO ACTION ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
デビッド・スピレットの答えは、「励まし」の提案を除いて、すべての点で正しいです。
これは、推奨するだけでなく、(ほとんどすべてのバージョンで)オプティマイザに必要なインデックスを使用して50行を検索するプランを選択させる方法です。その後、結合を実行します。常に使用できるわけではありませんが、FOREIGN KEY
制約は、この場合、2つのクエリが同じ結果を生成することを保証します。
私はこの手法を呼び出します "最初にLIMIT
、次にJOIN
" =:
SELECT p.*, ps.*
FROM ( SELECT *
FROM productStore
ORDER BY storeTitle
LIMIT 50
) ps
INNER JOIN product p
ON p.productUUID = ps.productUUID
ORDER BY ps.storeTitle ;
本当の答えは、「プレフィックス」インデックスは事実上役に立たないということです。私は言及しています
_KEY `testStoreTitle` (`storeTitle`(182))
_
インデックスには切り捨てられた値のみが含まれるため、notには完全に順序付けされたタイトルのリストが含まれるため、_ORDER BY
_を行うために簡単に使用することはできません。
InnoDBには767の制限がありますbytes(utf8 VARCHAR(255)
の最大値)。これは、複雑な一連の手順で増やすことができます。
SET GLOBAL innodb_file_format=Barracuda;
_SET GLOBAL innodb_file_per_table=ON;
_ALTER TABLE tbl DROP INDEX testStoreTitle, ADD INDEX(storeTitle) ROW_FORMAT=DYNAMIC;
またはCOMPRESSED
)Ypercubeが提案する「JOINの前のLIMIT(またはGROUP BY)」に同意します。そのソリューションは、ほとんどこのソリューションに直交しています。私のソリューションは---(おそらく 942187をスキャンする必要がないため、大幅に高速になります。
「なぜこんなに時間がかかるのか」はUsing filesort
-これは、すべての結果をスプールして、インデックスなしでソートすることを意味します。
問題は、SQLエンジンがテーブル参照ごとに1つのインデックスしか使用できないことです。この場合、結合述語に最適なインデックス(主キーのインデックス)を使用します。
IIRC mySqlのクエリプランナーは、効率を上げるために賢く並べ替えようとするのではなく、クエリの順序に従います。したがって、結果を50に制限したとしても、最初にすべてを結合する必要があります(他のキーを使用)、並べ替え、次に最初の50を選択します。productStoreを順にスキャンしてから、次のようにして製品に必要なものを検索できます。
SELECT *
FROM productStore ps
INNER JOIN product p
ON p.productUUID = ps.productUUID
ORDER BY ps.storeTitle
LIMIT 50;
productStore.storeTitle
最初の50を取得するためのインデックスthenそれぞれをproduct
の関連する行に結合します。そうでない場合は、ypercubeの派生テーブルベースのソリューションが望ましい効果をもたらす可能性があります(私はMS SQL Serverを使用する傾向があり、そのクエリプランナーは、私のクエリと彼の両方が同等であり、同じ方法で両方を実行できるように十分明るいと思います、しかしmySQLはおそらくそのようなトリックをあまり知っていません)。
列は異なる順序で出力されるため、呼び出しコードがこれに敏感である場合は注意してください。 *
select句では、他の理由からこの理由により回避することは実用的ではありません。
インデックスは、行を選択(検索)するためにのみ使用されます。それらが最終的な結果セットに入れられると、それらはどこに来たかに関係なく、単に行と列のセットになります。 (ソート)による順序付けが、この結果セットの最後の操作として適用されます。あなたのケースでは、非常に大きな結果セットを取得しています。明らかに、それを並べ替えようとすると、クエリ時間が増加します。