Bug #80298 Full-Text queries with additional secondary index gives NULL or Zero rows
Submitted: 8 Feb 2016 0:35 Modified: 30 May 2016 13:34
Reporter: Ray Lambe Email Updates:
Status: Closed Impact on me:
None 
Category:MySQL Server: FULLTEXT search Severity:S2 (Serious)
Version:5.7.11 OS:Any
Assigned to: CPU Architecture:Any

[8 Feb 2016 0:35] Ray Lambe
Description:
Certain Full-Text queries are failing when an additional secondary index is used.

How to repeat:
I'll attach SQL below to this bugs.

1. Create test table/data

mysql> CREATE TABLE `ft_max_test` (
    ->   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    ->   `doc_id` int(10) unsigned NOT NULL DEFAULT '0',
    ->   `title` varchar(255) NOT NULL DEFAULT 'Untitled',
    ->   `body` mediumtext,
    ->   PRIMARY KEY (`id`),  
    ->   KEY `doc_id` (`doc_id`),  
    ->   FULLTEXT KEY `title` (`title`),  
    ->   FULLTEXT KEY `body` (`body`) 
    -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Query OK, 0 rows affected (0.04 sec)

mysql> INSERT INTO ft_max_test VALUES
    ->   (1,  1, 'Insert into table', 'insert into table select from'),
    ->   (2,  1, 'Delete from table', 'some body text here'),
    ->   (3,  1, 'Update',            'perform update'),
    ->   (4,  2, 'Insert into table', 'insert into table select from'),
    ->   (5,  2, 'Delete from table', 'some body text here'),
    ->   (6,  2, 'Update',            'perform update'),
    ->   (7,  3, 'Insert into table', 'insert into table select from'),
    ->   (8,  3, 'Delete from table', 'some body text here'),
    ->   (9,  3, 'Update',            'perform update'),
    ->   (10, 4, 'Insert into table', 'insert into table select from'),
    ->   (11, 4, 'Delete from table', 'some body text here'),
    ->   (12, 4, 'Update',            'perform update');
Query OK, 12 rows affected (0.00 sec)
Records: 12  Duplicates: 0  Warnings: 0

2. Perform FT search getting max relevance for title and body (without using doc_id index)

mysql> SELECT
    ->     MAX(MATCH (title) AGAINST ('table' IN BOOLEAN MODE)) AS rel1,
    ->     MAX(MATCH (body) AGAINST ('table' IN BOOLEAN MODE)) AS rel2
    -> FROM
    ->     ft_max_test
    -> WHERE
    ->     MATCH (title) AGAINST ('table' IN BOOLEAN MODE) OR
    ->     MATCH (body) AGAINST ('table' IN BOOLEAN MODE);
+----------------------+---------------------+
| rel1                 | rel2                |
+----------------------+---------------------+
| 0.031008131802082062 | 0.22764469683170319 |
+----------------------+---------------------+
1 row in set (0.01 sec)

3. Examine title and body relevance values for rows matched

mysql> SELECT
    ->     id,
    ->     doc_id,
    ->     MATCH (title) AGAINST ('table' IN BOOLEAN MODE) AS rel1,
    ->     MATCH (body) AGAINST ('table' IN BOOLEAN MODE) AS rel2
    -> FROM
    ->     ft_max_test
    -> WHERE
    ->     MATCH (title) AGAINST ('table' IN BOOLEAN MODE) OR
    ->     MATCH (body) AGAINST ('table' IN BOOLEAN MODE);
+----+--------+----------------------+---------------------+
| id | doc_id | rel1                 | rel2                |
+----+--------+----------------------+---------------------+
|  1 |      1 | 0.031008131802082062 | 0.22764469683170319 |
|  2 |      1 | 0.031008131802082062 |                   0 |
|  4 |      2 | 0.031008131802082062 | 0.22764469683170319 |
|  5 |      2 | 0.031008131802082062 |                   0 |
|  7 |      3 | 0.031008131802082062 | 0.22764469683170319 |
|  8 |      3 | 0.031008131802082062 |                   0 |
| 10 |      4 | 0.031008131802082062 | 0.22764469683170319 |
| 11 |      4 | 0.031008131802082062 |                   0 |
+----+--------+----------------------+---------------------+
8 rows in set (0.00 sec)

4. Perform FT search getting max relevance for title and body using doc_id index (Doesn't Work)

mysql> SELECT
    ->     MAX(MATCH (title) AGAINST ('table' IN BOOLEAN MODE)) AS rel1,
    ->     MAX(MATCH (body) AGAINST ('table' IN BOOLEAN MODE)) AS rel2
    -> FROM
    ->     ft_max_test
    -> WHERE
    ->     doc_id = 1 AND
    ->     (MATCH (title) AGAINST ('table' IN BOOLEAN MODE) OR
    ->     MATCH (body) AGAINST ('table' IN BOOLEAN MODE));
+------+------+
| rel1 | rel2 |
+------+------+
| NULL | NULL |
+------+------+
1 row in set (0.00 sec)

5. Run EXPLAIN to confirm doc_id index was used

mysql> EXPLAIN SELECT
    ->     MAX(MATCH (title) AGAINST ('table' IN BOOLEAN MODE)) AS rel1,
    ->     MAX(MATCH (body) AGAINST ('table' IN BOOLEAN MODE)) AS rel2
    -> FROM
    ->     ft_max_test
    -> WHERE
    ->     doc_id = 1 AND
    ->     (MATCH (title) AGAINST ('table' IN BOOLEAN MODE) OR
    ->     MATCH (body) AGAINST ('table' IN BOOLEAN MODE));
+----+-------------+-------------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
| id | select_type | table       | partitions | type | possible_keys | key    | key_len | ref   | rows | filtered | Extra       |
+----+-------------+-------------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | ft_max_test | NULL       | ref  | doc_id        | doc_id | 4       | const |    3 |    20.99 | Using where |
+----+-------------+-------------+------------+------+---------------+--------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

6. Try to get title and body relevance values for rows matched

mysql> SELECT
    ->     id,
    ->     doc_id,
    ->     MATCH (title) AGAINST ('table' IN BOOLEAN MODE) AS rel1,
    ->     MATCH (body) AGAINST ('table' IN BOOLEAN MODE) AS rel2
    -> FROM
    ->     ft_max_test
    -> WHERE
    ->     doc_id = 1 AND
    ->     (MATCH (title) AGAINST ('table' IN BOOLEAN MODE) OR
    ->     MATCH (body) AGAINST ('table' IN BOOLEAN MODE));
Empty set (0.00 sec)

7. Try 4 and 6 above again ignoring the doc_id index ... Works!

mysql> SELECT
    ->     MAX(MATCH (title) AGAINST ('table' IN BOOLEAN MODE)) AS rel1,
    ->     MAX(MATCH (body) AGAINST ('table' IN BOOLEAN MODE)) AS rel2
    -> FROM
    ->     ft_max_test
    -> IGNORE
    ->     index(doc_id)
    -> WHERE
    ->     doc_id = 1 AND
    ->     (MATCH (title) AGAINST ('table' IN BOOLEAN MODE) OR
    ->     MATCH (body) AGAINST ('table' IN BOOLEAN MODE));
+----------------------+---------------------+
| rel1                 | rel2                |
+----------------------+---------------------+
| 0.031008131802082062 | 0.22764469683170319 |
+----------------------+---------------------+
1 row in set (0.01 sec)

mysql> SELECT
    ->     id,
    ->     doc_id,
    ->     MATCH (title) AGAINST ('table' IN BOOLEAN MODE) AS rel1,
    ->     MATCH (body) AGAINST ('table' IN BOOLEAN MODE) AS rel2
    -> FROM
    ->     ft_max_test
    -> IGNORE
    ->     index(doc_id)
    -> WHERE
    ->     doc_id = 1 AND
    ->     (MATCH (title) AGAINST ('table' IN BOOLEAN MODE) OR
    ->     MATCH (body) AGAINST ('table' IN BOOLEAN MODE));
+----+--------+----------------------+---------------------+
| id | doc_id | rel1                 | rel2                |
+----+--------+----------------------+---------------------+
|  1 |      1 | 0.031008131802082062 | 0.22764469683170319 |
|  2 |      1 | 0.031008131802082062 |                   0 |
+----+--------+----------------------+---------------------+
2 rows in set (0.00 sec)

Suggested fix:
May be related to bug #80244.
[8 Feb 2016 0:35] Ray Lambe
Test Case

Attachment: test_case.sql (application/octet-stream, text), 6.09 KiB.

[8 Feb 2016 3:39] MySQL Verification Team
Hello Ray,

Thank you for the report and test case.
Observed this with 5.7.11 build.

Thanks,
Umesh
[30 May 2016 13:34] Daniel Price
Posted by developer:
 
Fixed as of the upcoming 5.7.14 release, and here's the changelog entry:

The optimizer failed to mark the columns required for MATCH function
evaluation when the secondary index used for the full-text query was not
chosen by the optimizer. 

Thank you for the bug report.