Bug #72866 Better explain output for index map
Submitted: 4 Jun 2014 9:20
Reporter: Daniël van Eeden (OCA) Email Updates:
Status: Open Impact on me:
None 
Category:MySQL Server: Optimizer Severity:S4 (Feature request)
Version:5.7.4-m14 OS:Any
Assigned to: CPU Architecture:Any
Tags: explain, json, range

[4 Jun 2014 9:20] Daniël van Eeden
Description:
The documentation for this feature:

http://dev.mysql.com/doc/refman/5.7/en/explain-output.html
----------------------------------------------------
Range checked for each record (index map: N)

MySQL found no good index to use, but found that some of indexes might be used after column values from preceding tables are known. For each row combination in the preceding tables, MySQL checks whether it is possible to use a range or index_merge access method to retrieve rows. This is not very fast, but is faster than performing a join with no index at all. The applicability criteria are as described in Section 8.2.1.3, “Range Optimization”, and Section 8.2.1.4, “Index Merge Optimization”, with the exception that all column values for the preceding table are known and considered to be constants.

Indexes are numbered beginning with 1, in the same order as shown by SHOW INDEX for the table. The index map value N is a bitmask value that indicates which indexes are candidates. For example, a value of 0x19 (binary 11001) means that indexes 1, 4, and 5 will be considered. 
----------------------------------------------------

The part from the json explain:
----------------------------------------------------
      {
        "table": {
          "table_name": "l",
          "access_type": "ALL",
          "possible_keys": [
            "left_right_index"
          ],
          "rows_examined_per_scan": 11,
          "rows_produced_per_join": 44,
          "filtered": 100,
          "range_checked_for_each_record": "index map: 0x2",
          "cost_info": {
            "read_cost": "1.00",
            "eval_cost": "8.80",
            "prefix_cost": "11.61",
            "data_read_per_join": "1K"
          },
          "used_columns": [
            "id",
            "lft",
            "rght",
            "node_depth"
          ]
        }
----------------------------------------------------

And from the traditional explain:
----------------------------------------------------
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: l
   partitions: NULL
         type: ALL
possible_keys: left_right_index
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 11
     filtered: 100.00
        Extra: Range checked for each record (index map: 0x2)
----------------------------------------------------

The index map bitmap makes sense for the one-line per entry, but not for the json explain.

It should resolve the tables in the index map.

How to repeat:
CREATE TABLE `docnode_tree` (
  `id` int(10) unsigned NOT NULL,
  `lft` int(10) unsigned DEFAULT NULL,
  `rght` int(10) unsigned DEFAULT NULL,
  `node_depth` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `left_right_index` (`lft`,`rght`),
  KEY `right_index` (`rght`),
  KEY `node_depth_index` (`node_depth`),
  KEY `dept_l` (`node_depth`,`lft`),
  KEY `dept_r` (`node_depth`,`rght`),
  KEY `dept_lr` (`node_depth`,`lft`,`rght`),
  KEY `dept_rl` (`node_depth`,`rght`,`lft`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `docnode_tree` VALUES (1,10,20,0),(2,8,10,1),(3,11,15,1),(4,16,20,1),(5,21,25,1),(6,8,9,2),(7,9,10,2),(8,10,11,2),(9,11,12,2),(10,12,13,2),(11,13,14,2);
explain format=json SELECT * FROM docnode_tree r JOIN docnode_tree l ON l.lft BETWEEN r.lft AND r.rght WHERE r.node_depth=1\G
explain SELECT * FROM docnode_tree r JOIN docnode_tree l ON l.lft BETWEEN r.lft AND r.rght WHERE r.node_depth=1\G

Suggested fix:
Solution 1: Add the index map
"range_checked_for_each_record": "index map: 0x2",
"index_map_info": {
  "0x1": "foo_index",
  "0x2": "bar_index"
  "0x3": {"foo_index", "bar_index"}
}
Listing all combinations makes it easier for the tools, but might create a large list.

Solution 2: Resolve the map and display only the used indices
"range_checked_for_each_record": {
  "foo_index",
  "bar_index"
}