【问题标题】:Understanding how index works了解索引的工作原理
【发布时间】:2023-01-13 06:39:54
【问题描述】:

表定义

CREATE TABLE `prospectos` (
  `provincia` tinyint(3) unsigned NOT NULL,
  `id` int(8) unsigned NOT NULL AUTO_INCREMENT,
  `nombre` varchar(60) COLLATE utf8_bin NOT NULL,
  `telefono_fijo` varchar(15) COLLATE utf8_bin NOT NULL,
  `telefono_movil` varchar(15) COLLATE utf8_bin NOT NULL,
  PRIMARY KEY (`id`,`provincia`),
  KEY `nombre_idx` (`nombre`),
  KEY `tel_fijo_idx` (`provincia`,`telefono_fijo`) USING BTREE,
  KEY `tel_movil_idx` (`provincia`,`telefono_movil`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=35142 DEFAULT CHARSET=utf8 COLLATE=utf8_bin
 PARTITION BY RANGE (`provincia`)
(PARTITION `p01` VALUES LESS THAN (2) ENGINE = InnoDB,
..
..
..
PARTITION `p24` VALUES LESS THAN (25) ENGINE = InnoDB,
PARTITION `p99` VALUES LESS THAN MAXVALUE ENGINE = InnoDB)

进行此搜索是使用主键中的两个字段

说明 format=json select * from provistos where provincia = 20 and id = 23;

query_block": {
    "select_id": 1,
    "table": {
      "table_name": "prospectos",
      "partitions": ["p20"],
      "access_type": "const",
      "possible_keys": ["PRIMARY", "tel_fijo_idx", "tel_movil_idx"],
      "key": "PRIMARY",
      "key_length": "5",
      "used_key_parts": ["id", "provincia"],
      "ref": ["const", "const"],
      "rows": 1,
      "filtered": 100

现在做另一个选择使用不同的键为什么不使用这两个字段?

说明 format=json select * from provistos where provincia = 20 and telefono_fijo = 3424527000;

"query_block": {
    "select_id": 1,
    "table": {
      "table_name": "prospectos",
      "partitions": ["p20"],
      "access_type": "ref",
      "possible_keys": ["tel_fijo_idx", "tel_movil_idx"],
      "key": "tel_fijo_idx",
      "key_length": "1",
      "used_key_parts": ["provincia"],
      "ref": ["const"],
      "rows": 16042,
      "filtered": 100,
      "attached_condition": "prospectos.telefono_fijo = 3424527000"

它仅使用 tel_fijo_idx 上的 provincia 而不是 telefono_fijo 进行搜索。

为什么此搜索不使用这两个字段???

【问题讨论】:

    标签: mysql mariadb


    【解决方案1】:

    因为查询中的 telefono_fijo 未被引用,所以被视为数字。

    因为type conversion。 “如果一个参数是字符串而另一个参数是整数,则将它们作为小数进行比较”(来自 MariaDB-10.3.36(以及上个月的其他版本))。在旧版本中,它们将作为浮点实数进行比较。

    因此,对于您的查询,MariaDB 需要将每个 telefono_fijo 转换为十进制(如果更早,则为浮点数),并且由于发生了转换,因此未使用索引的这一方面。

    一种解决方案是在查询中引用 telefono_fijo 以将其视为字符串。如果电话号码可以包含前导 0 或其他非数字字符,这很好。即使在这种情况下,使用 utf8 也可能过多,而 ascii/latin/binary 字符集就足够了。

    explain format=json select * from prospectos where provincia = 20 and telefono_fijo = "3424527000"
    
    {
    "query_block": {
    "select_id": 1,
    "table": {
    "table_name": "prospectos",
    "access_type": "ref",
    "possible_keys": ["tel_fijo_idx", "tel_movil_idx"],
    "key": "tel_fijo_idx",
    "key_length": "48",
    "used_key_parts": ["provincia", "telefono_fijo"],
    "ref": ["const", "const"],
    "rows": 1,
    "filtered": 100,
    "index_condition": "prospectos.telefono_fijo = '3424527000'"
    }
    }
    }
    

    (10.3.36 和 2022 年 8 月 15 日或之后发布的其他版本(release notes for date))的另一个解决方案是使用 telefono_finjo 的十进制类型,假设不带前导 0 的数字是数据集。 (alter table prospectos modify telefono_fijo decimal(15) NOT NULL)。完成后,将使用索引(注意 explain 中的 used_key_parts),无论 telefono_fijo 的常量过滤器是像字符串一样被引用,还是像数字一样不被引用。

    explain format=json select * from prospectos where provincia = 20 and telefono_fijo = 3424527000
    
    {
    "query_block": {
    "select_id": 1,
    "table": {
    "table_name": "prospectos",
    "access_type": "ref",
    "possible_keys": ["tel_fijo_idx", "tel_movil_idx"],
    "key": "tel_fijo_idx",
    "key_length": "8",
    "used_key_parts": ["provincia", "telefono_fijo"],
    "ref": ["const", "const"],
    "rows": 1,
    "filtered": 100
    }
    }
    }
    

    参考:fiddle

    【讨论】:

    • 优秀的信息,我将保留电话为 varchar,因为我认为比数字类型更灵活,选择使用引号,我是 php + mysql 的新手,我不注意这种细节。
    • 当转换是自动的时,大多数时候很容易忘记它:-)。请考虑将 latin1 作为它的字符集,但是为了节省空间和比较速度。
    【解决方案2】:

    我建议摆脱分区和替换

    PRIMARY KEY (`id`,`provincia`),
    KEY `tel_fijo_idx` (`provincia`,`telefono_fijo`) USING BTREE,
    KEY `tel_movil_idx` (`provincia`,`telefono_movil`) USING BTREE
    

    PRIMARY KEY (`provincia`, id),
    INDEX(id)
    KEY `tel_fijo_idx` (`telefono_fijo`) USING BTREE,
    KEY `tel_movil_idx` (`telefono_movil`) USING BTREE
    

    新的 PK 是按照通常需要的顺序来获取的。在id 上进行攻击使 PK 独一无二(PK 的要求)。这也使得其他两个索引变得不必要(至少对于

    id 上的索引(或至少以id 开头)让AUTO_INCREMENT 开心。

    其他两个索引与您的类似,即使没有分区也一样快。

    请参见维基百科中的“B+树”。

    VARCHAR 与未加引号的数字字符串进行比较可防止使用索引。 (其他组合工作正常。)这解释了你的问题之一——telefono_fijo = 3424527000

    回到你的问题:

    通过分区,这里概述了您的 SELECT 是如何工作的:

    1. 如果可能,进行“分区修剪”。在您的两个示例 ("partitions": ["p20"]) 中,有可能表明只使用了一个分区。
    2. 使用INDEX(或PRIMARY KEY)定位所需的行。注意:索引是特定于一个分区的。
    3. 如果使用了多个分区(不是你的情况),合并每个分区的结果。
    4. 申请GROUPORDERLIMIT(不是你的情况)。

      没有分区,但有我的索引:

      1. 使用基于电话号码的二级索引。注意:在 InnoDB 中,PK 隐式附加到索引上,因此 id 也可用于过滤。
      2. 该索引行将指向数据 BTree 中的行。
      3. 申请GROUPORDERLIMIT(不是你的情况)。请注意,如果您按编号分组或排序,请注意可能不需要此额外步骤。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-02-20
      • 2012-12-11
      • 2011-02-18
      • 2018-02-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多