为什么索引不与 order by 一起使用?

Par*_*ser 5 mysql index order-by

我试图获取有关为什么 MySQL 在创建索引时不使用我的索引inner joinORDER BY最终尝试使用的信息。

我在这里有我的 SQL 查询:

SELECT
    *           
FROM
    product p INNER JOIN productStore ps ON p.productUUID = ps.productUUID       
ORDER BY
    ps.storeTitle 
LIMIT 50;
Run Code Online (Sandbox Code Playgroud)

当我通过这个选择使用订单超过 3,5 秒时,当我通过它花费 1,6 毫秒来删除订单以运行相同的 SQL 时,我的解释 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
Run Code Online (Sandbox Code Playgroud)

没有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
Run Code Online (Sandbox Code Playgroud)

没有索引的字段是长度为 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;
Run Code Online (Sandbox Code Playgroud)

产品表:

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;
Run Code Online (Sandbox Code Playgroud)

ype*_*eᵀᴹ 5

除了“令人鼓舞”的建议外,David Spillett 的回答在所有方面都是正确的。

这是一种不仅鼓励而且(在几乎所有版本中)强制优化器选择使用所需索引来查找 50 行的计划的方法 - 并且仅在此之后执行连接。它不能总是使用,但FOREIGN KEY约束确保在这种情况下两个查询将产生相同的结果。
我称这种技术为“先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 ;
Run Code Online (Sandbox Code Playgroud)

  • 不清楚您如何使用此功能。它可能禁止在列中使用索引,但如果您发布一个包含所有详细信息(函数、查询等)的新问题会更好。 (2认同)

Ric*_*mes 5

真正的答案是“前缀”索引实际上毫无用处。我指的是

KEY `testStoreTitle` (`storeTitle`(182))
Run Code Online (Sandbox Code Playgroud)

由于索引仅包含截断的值,因此它没有完全排序的标题列表,因此不能轻松地用于执行ORDER BY.

InnoDB 的限制为 767字节(utf8 的最大值VARCHAR(255))。这可以通过一组复杂的步骤来增加:

  1. 获取 5.6.3(或更高版本)。
  2. SET GLOBAL innodb_file_format=Barracuda;
  3. SET GLOBAL innodb_file_per_table=ON;
  4. ALTER TABLE tbl DROP INDEX testStoreTitle, ADD INDEX(storeTitle) ROW_FORMAT=DYNAMIC; - (或者COMPRESSED

我同意 ypercube 建议的“加入之前限制(或分组依据)”。该解决方案大部分与此解决方案正交。我的解决方案可能快得多,因为它不需要扫描 942187 任何东西。