Bug #47963 Wrong results when index is used
Submitted: 10 Oct 1:07 Modified: 17 Nov 17:48
Reporter: Todd Farmer
Status: Closed
Category:Server: Optimizer Severity:S1 (Critical)
Version:5.1.39, 5.1.41 OS:Any
Assigned to: Ramil Kalimullin Target Version:5.1.41+
Tags: regression
Triage: Triaged: D2 (Serious)

[10 Oct 1:07] Todd Farmer
Description:
Beginning in 5.1.39, queries against InnoDB tables with certain composite keys can
produce incorrect results:

mysql> SELECT VERSION();
+----------------------+
| VERSION()            |
+----------------------+
| 5.1.39-community-log |
+----------------------+
1 row in set (0.00 sec)

mysql>
mysql> DROP TABLE IF EXISTS not_found;
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE TABLE `not_found` (
    ->   `col1` varchar(5) NOT NULL DEFAULT '',
    ->   `col2` varchar(15) NOT NULL DEFAULT '',
    ->   `col3` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
    ->   KEY `col2` (`col1`, `col2`, `col3`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Query OK, 0 rows affected (0.01 sec)

mysql>
mysql> INSERT INTO not_found (col1, col2, col3) VALUES
    -> ('TEST', 'TEST', '2009-10-09 00:00:00');
Query OK, 1 row affected (0.00 sec)

mysql> -- not found!
mysql> SELECT * FROM not_found
    -> WHERE col1 = 'TEST'
    -> AND col3 >= '2009-10-09 00:00:00.0'
    -> AND col3 <= '2009-10-09 00:00:00.0';
Empty set (0.00 sec)

mysql>
mysql> -- equivalent logic finds row:
mysql> SELECT * FROM not_found
    -> WHERE col1 = 'TEST'
    -> AND col3 = '2009-10-09 00:00:00.0';
+------+------+---------------------+
| col1 | col2 | col3                |
+------+------+---------------------+
| TEST | TEST | 2009-10-09 00:00:00 |
+------+------+---------------------+
1 row in set (0.00 sec)

mysql>
mysql> -- dropping the key finds it
mysql> ALTER TABLE not_found DROP KEY `col2`;
Query OK, 1 row affected (0.02 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM not_found
    -> WHERE col1 = 'TEST'
    -> AND col3 >= '2009-10-09 00:00:00.0'
    -> AND col3 <= '2009-10-09 00:00:00.0';
+------+------+---------------------+
| col1 | col2 | col3                |
+------+------+---------------------+
| TEST | TEST | 2009-10-09 00:00:00 |
+------+------+---------------------+
1 row in set (0.00 sec)

mysql>
mysql> -- an index on just col1 and col2 doesn't cause the problem:
mysql> ALTER TABLE not_found ADD KEY `col2` (`col1`,`col2`);
Query OK, 1 row affected (0.02 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM not_found
    -> WHERE col1 = 'TEST'
    -> AND col3 >= '2009-10-09 00:00:00.0'
    -> AND col3 <= '2009-10-09 00:00:00.0';
+------+------+---------------------+
| col1 | col2 | col3                |
+------+------+---------------------+
| TEST | TEST | 2009-10-09 00:00:00 |
+------+------+---------------------+
1 row in set (0.00 sec)

mysql> -- but an index on all three does:
mysql> ALTER TABLE not_found DROP KEY `col2`;
Query OK, 1 row affected (0.01 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> ALTER TABLE not_found ADD KEY `col2` (`col1`,`col2`, `col3`);
Query OK, 1 row affected (0.02 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM not_found
    -> WHERE col1 = 'TEST'
    -> AND col3 >= '2009-10-09 00:00:00.0'
    -> AND col3 <= '2009-10-09 00:00:00.0';
Empty set (0.00 sec)

mysql>
mysql> -- so does an index on first and third columns:
mysql> ALTER TABLE not_found DROP KEY `col2`;
Query OK, 1 row affected (0.02 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> ALTER TABLE not_found ADD KEY `col2` (`col1`, `col3`);
Query OK, 1 row affected (0.01 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM not_found
    -> WHERE col1 = 'TEST'
    -> AND col3 >= '2009-10-09 00:00:00.0'
    -> AND col3 <= '2009-10-09 00:00:00.0';
Empty set (0.00 sec)

mysql>
mysql> EXPLAIN SELECT * FROM not_found
    -> WHERE col1 = 'TEST'
    -> AND col3 >= '2009-10-09 00:00:00'
    -> AND col3 <= '2009-10-09 00:00:00.0'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: not_found
         type: range
possible_keys: col2
          key: col2
      key_len: 15
          ref: NULL
         rows: 1
        Extra: Using where
1 row in set (0.03 sec)

mysql>
mysql> -- eliminating the milliseconds component eliminates problem:
mysql> SELECT * FROM not_found
    -> WHERE col1 = 'TEST'
    -> AND col3 >= '2009-10-09 00:00:00'
    -> AND col3 <= '2009-10-09 00:00:00';
+------+------+---------------------+
| col1 | col2 | col3                |
+------+------+---------------------+
| TEST | TEST | 2009-10-09 00:00:00 |
+------+------+---------------------+
1 row in set (0.03 sec)

mysql>
mysql> -- MyISAM isn't affected:
mysql> ALTER TABLE not_found ENGINE = MyISAM;
Query OK, 1 row affected (0.06 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM not_found
    -> WHERE col1 = 'TEST'
    -> AND col3 >= '2009-10-09 00:00:00.0'
    -> AND col3 <= '2009-10-09 00:00:00.0';
+------+------+---------------------+
| col1 | col2 | col3                |
+------+------+---------------------+
| TEST | TEST | 2009-10-09 00:00:00 |
+------+------+---------------------+
1 row in set (0.00 sec)

This is particularly problematic because C/J (and perhaps other drivers) explicitly add
the millisecond component when converting java.sql.Timestamp objects to strings.

How to repeat:
DROP TABLE IF EXISTS not_found;
CREATE TABLE `not_found` (
  `col1` varchar(5) NOT NULL DEFAULT '',
  `col2` varchar(15) NOT NULL DEFAULT '',
  `col3` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  KEY `col2` (`col1`, `col2`, `col3`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

INSERT INTO not_found (col1, col2, col3) VALUES 
('TEST', 'TEST', '2009-10-09 00:00:00');
-- not found!
SELECT * FROM not_found 
WHERE col1 = 'TEST'
AND col3 >= '2009-10-09 00:00:00.0' 
AND col3 <= '2009-10-09 00:00:00.0';

Suggested fix:
Produce correct results.
[10 Oct 1:16] Todd Farmer
Not limited to composite indexes:

mysql> DROP TABLE IF EXISTS not_found;
Query OK, 0 rows affected (0.00 sec)

mysql> CREATE TABLE `not_found` (
    ->   `col1` varchar(5) NOT NULL DEFAULT '',
    ->   `col2` varchar(15) NOT NULL DEFAULT '',
    ->   `col3` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
    ->   KEY `col3` (`col3`)
    -> ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Query OK, 0 rows affected (0.00 sec)

mysql>
mysql> INSERT INTO not_found (col1, col2, col3) VALUES
    -> ('TEST', 'TEST', '2009-10-09 00:00:00');
Query OK, 1 row affected (0.00 sec)

mysql> -- not found!
mysql> SELECT * FROM not_found
    -> WHERE col1 = 'TEST'
    -> AND col3 >= '2009-10-09 00:00:00.0'
    -> AND col3 <= '2009-10-09 00:00:00.0';
Empty set (0.00 sec)

mysql>
mysql> SELECT * FROM not_found
    -> WHERE col1 = 'TEST'
    -> AND col3 + INTERVAL 0 SECOND >= '2009-10-09 00:00:00.0'
    -> AND col3 + INTERVAL 0 SECOND <= '2009-10-09 00:00:00.0';
+------+------+---------------------+
| col1 | col2 | col3                |
+------+------+---------------------+
| TEST | TEST | 2009-10-09 00:00:00 |
+------+------+---------------------+
1 row in set (0.00 sec)
[11 Oct 10:53] Calvin Sun
This appears to be a server bug. There is no change in the InnoDB from 5.1.38 to 5.1.39.
And I use the latest InnoDB 5.1 repo with 5.1.38, the result is correct.
[11 Oct 11:37] Valeriy Kravchuk
Verified with recent 5.1.41 from bzr also. Indeed, repeatable only with InnoDB tables, but
this is an optimizer bug for sure. Look:

mysql> explain SELECT * FROM not_found WHERE col1 = 'TEST' AND col3 >= '2009-10-09
00:00:00.0' AND col3 <= '2009-10-09 00:00:00.0'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: NULL
         type: NULL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: NULL
        Extra: Impossible WHERE noticed after reading const tables
1 row in set (0.00 sec)

mysql> explain SELECT * FROM not_found IGNORE INDEX(col2) WHERE col1 = 'TEST' AND col3 >=
'2009-10-09 00:00:00.0' AND col3 <= '2009-10-09 00:00:00.0'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: not_found
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1
        Extra: Using where
1 row in set (0.00 sec)

mysql> SELECT * FROM not_found IGNORE INDEX(col2) WHERE col1 = 'TEST' AND col3 >=
'2009-10-09 00:00:00.0' AND col3 <= '2009-10-09 00:00:00.0'\G
*************************** 1. row ***************************
col1: TEST
col2: TEST
col3: 2009-10-09 00:00:00
1 row in set (0.00 sec)

mysql> select version();
+--------------+
| version()    |
+--------------+
| 5.1.41-debug |
+--------------+
1 row in set (0.00 sec)

mysql> alter table not_found engine=MyISAM;
Query OK, 1 row affected (0.41 sec)
Records: 1  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM not_found WHERE col1 = 'TEST' AND col3 >= '2009-10-09 00:00:00.0'
AND col3 <= '2009-10-09 00:00:00.0'\G
*************************** 1. row ***************************
col1: TEST
col2: TEST
col3: 2009-10-09 00:00:00
1 row in set (0.00 sec)

mysql> explain SELECT * FROM not_found WHERE col1 = 'TEST' AND col3 >= '2009-10-09
00:00:00.0' AND col3 <= '2009-10-09 00:00:00.0'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: not_found
         type: system
possible_keys: col2
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1
        Extra: 
1 row in set (0.00 sec)
[12 Oct 20:41] Ramil Kalimullin
The regression was introduced by fix for bug#46362.
Looks like it doesn't take into account milliseconds component somehow.
[13 Oct 6:43] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/86638

3163 Ramil Kalimullin	2009-10-13
      Fix for bug#47963: Wrong results when index is used
      
      Problem: using null microsecond part (e.g. "YYYY-MM-DD HH:MM:SS.0000") 
      in a WHERE condition may lead to wrong results due to improper
      DATETIMEs comparison in some cases.
      
      Fix: as we compare DATETIMEs as strings we must trim trailing 0's
      in such cases.
     @ mysql-test/r/innodb_mysql.result
        Fix for bug#47963: Wrong results when index is used
          - test result.
     @ mysql-test/t/innodb_mysql.test
        Fix for bug#47963: Wrong results when index is used
          - test case.
     @ sql/item.cc
        Fix for bug#47963: Wrong results when index is used
          - comparing DATETIMEs trim trailing 0's in the 
        microsecond part.
[18 Oct 18:27] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/87243

3182 Ramil Kalimullin	2009-10-18 [merge]
      Fix for bug#47963: Wrong results when index is used
      
      Problem: using null microsecond part in a WHERE condition 
      (e.g. WHERE date_time_field <= "YYYY-MM-DD HH:MM:SS.0000") 
      may lead to wrong results due to improper DATETIMEs 
      comparison in some cases.
      
      Fix: comparing DATETIMEs as strings we must trim trailing 0's
      in such cases.
     @ mysql-test/r/innodb_mysql.result
        Fix for bug#47963: Wrong results when index is used
          - test result.
     @ mysql-test/t/innodb_mysql.test
        Fix for bug#47963: Wrong results when index is used
          - test case.
     @ sql/item.cc
        Fix for bug#47963: Wrong results when index is used
          - comparing DATETIMEs as strings we must trim trailing 0's in the 
        microsecond part to ensure
        'YYYY-MM-DD HH:MM:SS.000' == 'YYYY-MM-DD HH:MM:SS'
[4 Nov 10:25] Bugs System
Pushed into 5.1.41 (revid:joro@sun.com-20091104092152-qz96bzlf2o1japwc) (version source
revid:ramil@mysql.com-20091018162655-z4dlolfx5s0zem8l) (merge vers: 5.1.41) (pib:13)
[11 Nov 7:53] Bugs System
Pushed into 6.0.14-alpha (revid:alik@sun.com-20091110093407-rw5g8dys2baqkt67) (version
source revid:alik@sun.com-20091109080109-7dxapd5y5pxlu08w) (merge vers: 6.0.14-alpha)
(pib:13)
[11 Nov 7:58] Bugs System
Pushed into 5.5.0-beta (revid:alik@sun.com-20091109115615-nuohp02h8mdrz8m2) (version
source revid:alik@sun.com-20091105090203-cls5j6k3ohu04xpt) (merge vers: 5.5.0-beta)
(pib:13)
[17 Nov 17:48] Paul DuBois
Noted in 5.1.41, 5.5.0, 6.0.14 changelogs.

In some cases, using a null microsecond part in a WHERE condition
(for example, WHERE date_time_field <= 'YYYY-MM-DD HH:MM:SS.0000')
could lead to incorrect results due to improper DATETIME comparison.