zoukankan      html  css  js  c++  java
  • 表数据量影响MySQL索引选择

    现象

    新建了一张员工表,插入了少量数据,索引中所有的字段均在where条件出现时,正确走到了idx_nap索引,但是where出现部分自左开始的索引时,却进行全表扫描,与MySQL官方所说的最左匹配原则“相悖”。

    数据背景

    CREATE TABLE `staffs` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(24) NOT NULL DEFAULT '' COMMENT '姓名',
      `age` int(11) NOT NULL DEFAULT '0' COMMENT '年龄',
      `pos` varchar(20) NOT NULL DEFAULT '' COMMENT '职位',
      `add_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间',
      PRIMARY KEY (`id`),
      KEY `idx_nap` (`name`,`age`,`pos`)
    ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COMMENT='员工记录表';
    
    表中数据如下:
    id	name	age	pos	    add_time
    1	July	23	dev	    2018-06-04 16:02:02
    2	Clive	22	dev	    2018-06-04 16:02:32
    3	Cleva	24	test	2018-06-04 16:02:38
    4	July	23	test	2018-06-04 16:12:22
    5	July	23	pre	    2018-06-04 16:12:37
    6	Clive	22	pre	    2018-06-04 16:12:48
    7	July	25	dev	    2018-06-04 16:30:17
    

    Explain语句看下执行计划

    -- 全匹配走了索引
    explain select * from staffs where name = 'July' and age = 23 and pos = 'dev';
    id	select_type	table	partitions	type	possible_keys	key	key_len	ref	rows	filtered	Extra
    1	SIMPLE	staffs	NULL	ref	idx_nap	idx_nap	140	const,const,const	1	100.00	NULL
    

    开启优化器跟踪优化过程

    -- 左侧部分匹配却没有走索引,全表扫描
    explain select * from staffs where name = 'July' and age = 23;
    id	select_type	table	partitions	type	possible_keys	key	key_len	ref	rows	filtered	Extra
    1	SIMPLE	staffs2	NULL	ALL	idx_nap	NULL	NULL	NULL	6	50.00	Using where
    
    -- 开启优化器跟踪
    set session optimizer_trace='enabled=on';
    -- 在执行完查询语句后,在执行以下的select语句可以查看具体的优化器执行过程
    select * from information_schema.optimizer_trace;
    

    Trace部分的内容

    {
      "steps": [
        {
          "join_preparation": {
            "select#": 1,
            "steps": [
              {
                "expanded_query": "/* select#1 */ select `staffs`.`id` AS `id`,`staffs`.`name` AS `name`,`staffs`.`age` AS `age`,`staffs`.`pos` AS `pos`,`staffs`.`add_time` AS `add_time` from `staffs` where ((`staffs`.`name` = 'July') and (`staffs`.`age` = 23))"
              }
            ]
          }
        },
        {
          "join_optimization": {
            "select#": 1,
            "steps": [
              {
                "condition_processing": {
                  "condition": "WHERE",
                  "original_condition": "((`staffs`.`name` = 'July') and (`staffs`.`age` = 23))",
                  "steps": [
                    {
                      "transformation": "equality_propagation",
                      "resulting_condition": "((`staffs`.`name` = 'July') and multiple equal(23, `staffs`.`age`))"
                    },
                    {
                      "transformation": "constant_propagation",
                      "resulting_condition": "((`staffs`.`name` = 'July') and multiple equal(23, `staffs`.`age`))"
                    },
                    {
                      "transformation": "trivial_condition_removal",
                      "resulting_condition": "((`staffs`.`name` = 'July') and multiple equal(23, `staffs`.`age`))"
                    }
                  ]
                }
              },
              {
                "substitute_generated_columns": {
                }
              },
              {
                "table_dependencies": [
                  {
                    "table": "`staffs`",
                    "row_may_be_null": false,
                    "map_bit": 0,
                    "depends_on_map_bits": [
                    ]
                  }
                ]
              },
              {
                "ref_optimizer_key_uses": [
                  {
                    "table": "`staffs`",
                    "field": "name",
                    "equals": "'July'",
                    "null_rejecting": false
                  },
                  {
                    "table": "`staffs`",
                    "field": "age",
                    "equals": "23",
                    "null_rejecting": false
                  }
                ]
              },
              {
                "rows_estimation": [
                  {
                    "table": "`staffs`",
                    "range_analysis": {
                      "table_scan": {
                        "rows": 6,
                        "cost": 4.3
                      },
                      "potential_range_indexes": [
                        {
                          "index": "PRIMARY",
                          "usable": false,
                          "cause": "not_applicable"
                        },
                        {
                          "index": "idx_nap",
                          "usable": true,
                          "key_parts": [
                            "name",
                            "age",
                            "pos",
                            "id"
                          ]
                        }
                      ],
                      "setup_range_conditions": [
                      ],
                      "group_index_range": {
                        "chosen": false,
                        "cause": "not_group_by_or_distinct"
                      },
                      "analyzing_range_alternatives": {
                        "range_scan_alternatives": [
                          {
                            "index": "idx_nap",
                            "ranges": [
                              "July <= name <= July AND 23 <= age <= 23"
                            ],
                            "index_dives_for_eq_ranges": true,
                            "rowid_ordered": false,
                            "using_mrr": false,
                            "index_only": false,
                            "rows": 3,
                            "cost": 4.61,
                            "chosen": false,
                            "cause": "cost"
                          }
                        ],
                        "analyzing_roworder_intersect": {
                          "usable": false,
                          "cause": "too_few_roworder_scans"
                        }
                      }
                    }
                  }
                ]
              },
              {
                "considered_execution_plans": [
                  {
                    "plan_prefix": [
                    ],
                    "table": "`staffs`",
                    "best_access_path": {
                      "considered_access_paths": [
                        {
                        //可以看到这边MySQL计算得到使用索引的成本为2.6
                          "access_type": "ref",
                          "index": "idx_nap",
                          "rows": 3,
                          "cost": 2.6,
                          "chosen": true
                        },
                        {
                        //而全表扫描计算所得的成本为2.2
                          "rows_to_scan": 6,
                          "access_type": "scan",
                          "resulting_rows": 6,
                          "cost": 2.2,
                          "chosen": true
                        }
                      ]
                    },
                    //因此选择了成本更低的scan
                    "condition_filtering_pct": 100,
                    "rows_for_plan": 6,
                    "cost_for_plan": 2.2,
                    "chosen": true
                  }
                ]
              },
              {
                "attaching_conditions_to_tables": {
                  "original_condition": "((`staffs`.`age` = 23) and (`staffs`.`name` = 'July'))",
                  "attached_conditions_computation": [
                  ],
                  "attached_conditions_summary": [
                    {
                      "table": "`staffs`",
                      "attached": "((`staffs`.`age` = 23) and (`staffs`.`name` = 'July'))"
                    }
                  ]
                }
              },
              {
                "refine_plan": [
                  {
                    "table": "`staffs`"
                  }
                ]
              }
            ]
          }
        },
        {
          "join_execution": {
            "select#": 1,
            "steps": [
            ]
          }
        }
      ]
    }
    

    增加表数据量

    -- 接下来增大表的数据量
    INSERT INTO `staffs` (`name`, `age`, `pos`, `add_time`)
    VALUES
    	('July', 25, 'dev', '2018-06-04 16:30:17'),
    	('July', 23, 'dev1', '2018-06-04 16:02:02'),
    	('July', 23, 'dev2', '2018-06-04 16:02:02'),
    	('July', 23, 'dev3', '2018-06-04 16:02:02'),
    	('July', 23, 'dev4', '2018-06-04 16:02:02'),
    	('July', 23, 'dev6', '2018-06-04 16:02:02'),
    	('July', 23, 'dev5', '2018-06-04 16:02:02'),
    	('July', 23, 'dev7', '2018-06-04 16:02:02'),
    	('July', 23, 'dev8', '2018-06-04 16:02:02'),
    	('July', 23, 'dev9', '2018-06-04 16:02:02'),
    	('July', 23, 'dev10', '2018-06-04 16:02:02'),
    	('Clive', 23, 'dev1', '2018-06-04 16:02:02'),
    	('Clive', 23, 'dev2', '2018-06-04 16:02:02'),
    	('Clive', 23, 'dev3', '2018-06-04 16:02:02'),
    	('Clive', 23, 'dev4', '2018-06-04 16:02:02'),
    	('Clive', 23, 'dev6', '2018-06-04 16:02:02'),
    	('Clive', 23, 'dev5', '2018-06-04 16:02:02'),
    	('Clive', 23, 'dev7', '2018-06-04 16:02:02'),
    	('Clive', 23, 'dev8', '2018-06-04 16:02:02'),
    	('Clive', 23, 'dev9', '2018-06-04 16:02:02'),
    	('Clive', 23, 'dev10', '2018-06-04 16:02:02');
    

    执行Explain

    -- 再次执行同样的查询语句,会发现走到索引上了
    explain select * from staffs where name = 'July' and age = 23;
    id	select_type	table	partitions	type	possible_keys	key	key_len	ref	rows	filtered	Extra
    1	SIMPLE	staffs	NULL	ref	idx_nap	idx_nap	78	const,const	13	100.00	NULL
    

    查看新的Trace内容

    -- 再看下优化器执行过程
    {
      "steps": [
        {
          "join_preparation": {
            "select#": 1,
            "steps": [
              {
                "expanded_query": "/* select#1 */ select `staffs`.`id` AS `id`,`staffs`.`name` AS `name`,`staffs`.`age` AS `age`,`staffs`.`pos` AS `pos`,`staffs`.`add_time` AS `add_time` from `staffs` where ((`staffs`.`name` = 'July') and (`staffs`.`age` = 23))"
              }
            ]
          }
        },
        {
          "join_optimization": {
            "select#": 1,
            "steps": [
              {
                "condition_processing": {
                  "condition": "WHERE",
                  "original_condition": "((`staffs`.`name` = 'July') and (`staffs`.`age` = 23))",
                  "steps": [
                    {
                      "transformation": "equality_propagation",
                      "resulting_condition": "((`staffs`.`name` = 'July') and multiple equal(23, `staffs`.`age`))"
                    },
                    {
                      "transformation": "constant_propagation",
                      "resulting_condition": "((`staffs`.`name` = 'July') and multiple equal(23, `staffs`.`age`))"
                    },
                    {
                      "transformation": "trivial_condition_removal",
                      "resulting_condition": "((`staffs`.`name` = 'July') and multiple equal(23, `staffs`.`age`))"
                    }
                  ]
                }
              },
              {
                "substitute_generated_columns": {
                }
              },
              {
                "table_dependencies": [
                  {
                    "table": "`staffs`",
                    "row_may_be_null": false,
                    "map_bit": 0,
                    "depends_on_map_bits": [
                    ]
                  }
                ]
              },
              {
                "ref_optimizer_key_uses": [
                  {
                    "table": "`staffs`",
                    "field": "name",
                    "equals": "'July'",
                    "null_rejecting": false
                  },
                  {
                    "table": "`staffs`",
                    "field": "age",
                    "equals": "23",
                    "null_rejecting": false
                  }
                ]
              },
              {
                "rows_estimation": [
                  {
                    "table": "`staffs`",
                    "range_analysis": {
                      "table_scan": {
                        "rows": 27,
                        "cost": 8.5
                      },
                      "potential_range_indexes": [
                        {
                          "index": "PRIMARY",
                          "usable": false,
                          "cause": "not_applicable"
                        },
                        {
                          "index": "idx_nap",
                          "usable": true,
                          "key_parts": [
                            "name",
                            "age",
                            "pos",
                            "id"
                          ]
                        }
                      ],
                      "setup_range_conditions": [
                      ],
                      "group_index_range": {
                        "chosen": false,
                        "cause": "not_group_by_or_distinct"
                      },
                      "analyzing_range_alternatives": {
                        "range_scan_alternatives": [
                          {
                            "index": "idx_nap",
                            "ranges": [
                              "July <= name <= July AND 23 <= age <= 23"
                            ],
                            "index_dives_for_eq_ranges": true,
                            "rowid_ordered": false,
                            "using_mrr": false,
                            "index_only": false,
                            "rows": 13,
                            "cost": 16.61,
                            "chosen": false,
                            "cause": "cost"
                          }
                        ],
                        "analyzing_roworder_intersect": {
                          "usable": false,
                          "cause": "too_few_roworder_scans"
                        }
                      }
                    }
                  }
                ]
              },
              {
                "considered_execution_plans": [
                  {
                    "plan_prefix": [
                    ],
                    "table": "`staffs`",
                    "best_access_path": {
                      "considered_access_paths": [
                        {
                        //使用索引的成本变为了5.3
                          "access_type": "ref",
                          "index": "idx_nap",
                          "rows": 13,
                          "cost": 5.3,
                          "chosen": true
                        },
                        {
                        //scan的成本变为了6.4
                          "rows_to_scan": 27,
                          "access_type": "scan",
                          "resulting_rows": 27,
                          "cost": 6.4,
                          "chosen": false
                        }
                      ]
                    },
                    //使用索引查询的成本更低,因此选择了走索引
                    "condition_filtering_pct": 100,
                    "rows_for_plan": 13,
                    "cost_for_plan": 5.3,
                    "chosen": true
                  }
                ]
              },
              {
                "attaching_conditions_to_tables": {
                  "original_condition": "((`staffs`.`age` = 23) and (`staffs`.`name` = 'July'))",
                  "attached_conditions_computation": [
                  ],
                  "attached_conditions_summary": [
                    {
                      "table": "`staffs`",
                      "attached": null
                    }
                  ]
                }
              },
              {
                "refine_plan": [
                  {
                    "table": "`staffs`"
                  }
                ]
              }
            ]
          }
        },
        {
          "join_execution": {
            "select#": 1,
            "steps": [
            ]
          }
        }
      ]
    }
    

    结论

    MySQL表数据量的大小,会影响索引的选择,具体的情况还是通过Explain和Optimizer Trace来查看与分析。

  • 相关阅读:
    linux内核中GNU C和标准C的区别
    linux内核中GNU C和标准C的区别
    Getting start with dbus in systemd (02)
    Getting start with dbus in systemd (01)
    Getting start with dbus in systemd (03)
    物理内存相关的三个数据结构
    数据类型对应字节数(32位,64位 int 占字节数)
    Linux kernel 内存
    共模电感的原理以及使用情况
    [原创]DC-DC输出端加电压会烧毁
  • 原文地址:https://www.cnblogs.com/clivewang/p/9863709.html
Copyright © 2011-2022 走看看