Bug #112181 TIMESTAMP(N) partitions doesn't prune for inequity
Submitted: 25 Aug 2023 9:04 Modified: 28 Aug 2023 7:41
Reporter: Ryo Wakatabe Email Updates:
Status: Verified Impact on me:
None 
Category:MySQL Server: Partitions Severity:S3 (Non-critical)
Version:8.0 OS:Any
Assigned to: CPU Architecture:Any

[25 Aug 2023 9:04] Ryo Wakatabe
Description:
Partitioning itself on TIMESTAMP(N) appears to work correctly [1] and partition pruning by equality works [3], but does not prune when it contains inequalities [4]. Partition pruning with TIMESTAMP (not TIMESTAMP(N)) works fine [5]. Full steps to reproduce on MySQL 5.7, 8.0 are attached. optimizer_trace didn't provide any meaningful information. A blog post in the past pointing out this issue has been published (http://minsql.com/mysql/MySQL-Fractional-timestamp-and-Partition-pruning/).

I understand https://dev.mysql.com/doc/refman/8.0/en/partitioning-range.html says "Any other expressions involving TIMESTAMP values are not permitted. (See Bug #42849.)", but the bug only mentions the need to have a UNIX_TIMESTAMP for timezone alignment, and it doesn't seem like partitioning with FLOOR would cause problems.

I also suspected it's because RANGE does not support non-integer types, according to "Other partitioning types require a partitioning expression that yields an integer value or NULL." /partitioning-types.html), but I don't believe this is the case because the input type is integer thanks to the FLOOR function in the definition of the partition.

TIMESTAMP partitions are often practically used for partitions by date and month. I think it's a big problem for such a heavily used feature to have such a huge performance impact due to silent optimizations not working. In fact, I'm working as a Technical Engineer for his GCP, and a Google Cloud Platform customer was hit by this misoptimization, causing some features in their production environment to go completely down under certain conditions.

Experiments were conducted on Cloud SQL for MySQL on GCP.

Questions:
Do you plan to support partitioning using TIMESTAMP(N) (not TIMESTAMP) in the future?

If yes, I think this silent and non-intuitive behavior should be fixed as a bug or documented that inequality partition pruning does not currently work. For example, users referencing the answer at https://stackoverflow.com/a/58339986 may silently hit this issue.

If no, the partitioning definition itself with TIMESTAMP(N) should be rejected as a syntax error.

Anyway, I think it's worth investigating why this problem occurs in any case.

How to repeat:
CREATE DATABASE test;
USE test;

CREATE TABLE pruning_test_tab (
id int NOT NULL,
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY RANGE (FLOOR(UNIX_TIMESTAMP(created_at))) (
PARTITION p202306 VALUES LESS THAN (UNIX_TIMESTAMP('2023-07-01')),
PARTITION p202307 VALUES LESS THAN (UNIX_TIMESTAMP('2023-08-01')),
PARTITION p202308 VALUES LESS THAN (UNIX_TIMESTAMP('2023-09-01')),
PARTITION p3 VALUES LESS THAN (MAXVALUE)
);

INSERT INTO pruning_test_tab values (1, '2023-06-01 08:30:00');
INSERT INTO pruning_test_tab values (2, '2023-07-01 08:30:00');
INSERT INTO pruning_test_tab values (3, '2023-08-01 08:30:00');
INSERT INTO pruning_test_tab values (4, '2023-09-01 08:30:00');

-- [1] Data is correctly partitioned
SELECT * FROM pruning_test_tab PARTITION (p202306);
SELECT * FROM pruning_test_tab PARTITION (p202307);
SELECT * FROM pruning_test_tab PARTITION (p202308);
SELECT * FROM pruning_test_tab PARTITION (p3);

-- [3] Partition pruning about equility is enforced.
EXPLAIN SELECT * FROM pruning_test_tab 
WHERE
created_at = '2023-08-01 08:30:00';

-- [4] Partition pruning about inequility is NOT enforced.
EXPLAIN SELECT * FROM pruning_test_tab 
WHERE
created_at < '2023-06-01 08:30:00';

CREATE DATABASE test;
USE test;

CREATE TABLE pruning_test_tab (
id int NOT NULL,
created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY RANGE (FLOOR(UNIX_TIMESTAMP(created_at))) (
PARTITION p202306 VALUES LESS THAN (UNIX_TIMESTAMP('2023-07-01')),
PARTITION p202307 VALUES LESS THAN (UNIX_TIMESTAMP('2023-08-01')),
PARTITION p202308 VALUES LESS THAN (UNIX_TIMESTAMP('2023-09-01')),
PARTITION p3 VALUES LESS THAN (MAXVALUE)
);

INSERT INTO pruning_test_tab values (1, '2023-06-01 08:30:00');
INSERT INTO pruning_test_tab values (2, '2023-07-01 08:30:00');
INSERT INTO pruning_test_tab values (3, '2023-08-01 08:30:00');
INSERT INTO pruning_test_tab values (4, '2023-09-01 08:30:00');

-- [1] Data is correctly partitioned
SELECT * FROM pruning_test_tab PARTITION (p202306);
SELECT * FROM pruning_test_tab PARTITION (p202307);
SELECT * FROM pruning_test_tab PARTITION (p202308);
SELECT * FROM pruning_test_tab PARTITION (p3);

-- [3] Partition pruning about equility is enforced.
EXPLAIN SELECT * FROM pruning_test_tab 
WHERE
created_at = '2023-08-01 08:30:00';

-- [4] Partition pruning about inequility is NOT enforced.
EXPLAIN SELECT * FROM pruning_test_tab 
WHERE
created_at < '2023-06-01 08:30:00';

CREATE TABLE pruning_test_tab2 (
id int NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY RANGE (UNIX_TIMESTAMP(created_at)) (
PARTITION p202306 VALUES LESS THAN (UNIX_TIMESTAMP('2023-07-01')),
PARTITION p202307 VALUES LESS THAN (UNIX_TIMESTAMP('2023-08-01')),
PARTITION p202308 VALUES LESS THAN (UNIX_TIMESTAMP('2023-09-01')),
PARTITION p3 VALUES LESS THAN (MAXVALUE)
);

INSERT INTO pruning_test_tab2 values (1, '2023-06-01 08:30:00');
INSERT INTO pruning_test_tab2 values (2, '2023-07-01 08:30:00');
INSERT INTO pruning_test_tab2 values (3, '2023-08-01 08:30:00');
INSERT INTO pruning_test_tab2 values (4, '2023-09-01 08:30:00');

SELECT * FROM pruning_test_tab2 PARTITION (p202306);
SELECT * FROM pruning_test_tab2 PARTITION (p202307);
SELECT * FROM pruning_test_tab2 PARTITION (p202308);
SELECT * FROM pruning_test_tab2 PARTITION (p3);

-- [5] Partition pruning DID WORK for TIMESTAMP (not TIMESTAMP(N))
SET optimizer_trace="enabled=on";
EXPLAIN SELECT * FROM pruning_test_tab2 
WHERE
created_at >= '2023-08-01 01:00:00' AND created_at <= '2023-08-02 01:00:00';
SELECT * FROM information_schema.optimizer_trace\G 

CREATE TABLE pruning_test_tab2 (
id int NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY RANGE (UNIX_TIMESTAMP(created_at)) (
PARTITION p202306 VALUES LESS THAN (UNIX_TIMESTAMP('2023-07-01')),
PARTITION p202307 VALUES LESS THAN (UNIX_TIMESTAMP('2023-08-01')),
PARTITION p202308 VALUES LESS THAN (UNIX_TIMESTAMP('2023-09-01')),
PARTITION p3 VALUES LESS THAN (MAXVALUE)
);

INSERT INTO pruning_test_tab2 values (1, '2023-06-01 08:30:00');
INSERT INTO pruning_test_tab2 values (2, '2023-07-01 08:30:00');
INSERT INTO pruning_test_tab2 values (3, '2023-08-01 08:30:00');
INSERT INTO pruning_test_tab2 values (4, '2023-09-01 08:30:00');

SELECT * FROM pruning_test_tab2 PARTITION (p202306);
SELECT * FROM pruning_test_tab2 PARTITION (p202307);
SELECT * FROM pruning_test_tab2 PARTITION (p202308);
SELECT * FROM pruning_test_tab2 PARTITION (p3);

-- [5] Partition pruning DID WORK for TIMESTAMP (not TIMESTAMP(N))
SET optimizer_trace="enabled=on";
EXPLAIN SELECT * FROM pruning_test_tab2 
WHERE
created_at >= '2023-08-01 01:00:00' AND created_at <= '2023-08-02 01:00:00';
SELECT * FROM information_schema.optimizer_trace\G 

[1] 
MySQL [test]> SELECT * FROM pruning_test_tab PARTITION (p202306)
    -> ;
+----+-------------------------+
| id | created_at              |
+----+-------------------------+
|  1 | 2023-06-01 08:30:00.000 |
+----+-------------------------+
1 row in set (0.002 sec)

MySQL [test]> SELECT * FROM pruning_test_tab PARTITION (p202307);
+----+-------------------------+
| id | created_at              |
+----+-------------------------+
|  2 | 2023-07-01 08:30:00.000 |
+----+-------------------------+
1 row in set (0.004 sec)

MySQL [test]> SELECT * FROM pruning_test_tab PARTITION (p202308);
+----+-------------------------+
| id | created_at              |
+----+-------------------------+
|  3 | 2023-08-01 08:30:00.000 |
+----+-------------------------+
1 row in set (0.002 sec)

MySQL [test]> SELECT * FROM pruning_test_tab PARTITION (p3);
+----+-------------------------+
| id | created_at              |
+----+-------------------------+
|  4 | 2023-09-01 08:30:00.000 |
+----+-------------------------+
1 row in set (0.002 sec)

[3]

+----+-------------+------------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table            | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+------------------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | pruning_test_tab | p202308    | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | Using where |
+----+-------------+------------------+------------+------+---------------+------+---------+------+------+----------+-------------+

[4]
+----+-------------+------------------+----------------------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table            | partitions                 | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+------------------+----------------------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | pruning_test_tab | p202306,p202307,p202308,p3 | ALL  | NULL          | NULL | NULL    | NULL |    4 |    33.33 | Using where |
+----+-------------+------------------+----------------------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.002 sec)

[5]
+----+-------------+-------------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table             | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------------------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | pruning_test_tab2 | p202308    | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | Using where |
+----+-------------+-------------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.001 sec)
[25 Aug 2023 9:27] Ryo Wakatabe
I'm aware of https://bugs.mysql.com/bug.php?id=66958 where it's marked as "Not a bug" with a questionable reason and a relationship with fractional UNIX_TIMESTAMP is suspected.
[25 Aug 2023 13:51] MySQL Verification Team
Hi Mr. Wakatebe,

Thank you very much for your bug report.

However, this is not a forum about future plans for MySQL.

Still, we have sent an e-mail to our Product Management and we are waiting for their response.

Please, have some patience.
[25 Aug 2023 13:55] Ryo Wakatabe
This is a bug report. Regardless of the future plans, I believe there is a product bug or doc bug. Please read "if yes", "if no" part.
[25 Aug 2023 13:57] MySQL Verification Team
Hi,

We do understand you, but we do not make decisions of that type.

Hence, please have some patience ......
[25 Aug 2023 14:01] Ryo Wakatabe
Please do not mark the status as Unsupported for a clear bug where "TIMESTAMP(N) partitions doesn't prune for inequity" as written in the title.
[25 Aug 2023 14:04] Ryo Wakatabe
It's totally OK as long as this properly handled, meaning it's not closed without any consideration. Thank you for involving the managmement team.
[25 Aug 2023 14:23] MySQL Verification Team
Hi Mr. Wakatabe,

We have managed to repeat the behaviour that you reported.

This is now a verified bug report.

We shall inform you on the destiny of this report when we get our replies.
[28 Aug 2023 7:06] huahua xu
Hi All,

The monotonicity of the internal function `Item_func_floor` is not specially defined, which is `NON_MONOTONIC` inherits from the `Item::get_monotonicity_info()`. Then the optimizer could not perform partition pruning for a given interval condition
[28 Aug 2023 7:41] Ryo Wakatabe
Thank you Huahua for your technical investigation!

According to https://github.com/mysql/mysql-server/blob/ea1efa9822d81044b726aab20c857d5e1b7e046a/sql/it... , the monotonicity is not well handled in MySQL currently, unfortunately.

```
At the moment function monotonicity is not well defined (and so may be incorrect) for Item trees with parameters/return types that are different from INT_RESULT, may be NULL, or are unsigned. It will be possible to address this issue once the related partitioning bugs (BUG#16002, BUG#15447, BUG#13436) are fixed.
```

FLOOR(TIMESTAMP(N)) (and FLOOR(T) where T is any other types) is mathematically a weekly increasing function and should be classified as MONOTONIC_INCREASING instead of NON_MONOTONIC, IIUC. I believe this is the "not well defined" and "incorrect" case. Should this be regarded as a bug and fixed in the future?

If this is the case, we may want to refer this bug in the comment for a reminder in the item.h like "the related partitioning bugs (BUG#16002, BUG#15447, BUG#13436)" in https://github.com/mysql/mysql-server/blob/ea1efa9822d81044b726aab20c857d5e1b7e046a/sql/it....
[30 Aug 2023 14:36] huahua xu
The patch would fix the bug.

Attachment: function_floor_without_monotonicity_define.patch (application/octet-stream, text), 1.84 KiB.

[18 Sep 2023 8:39] MySQL Verification Team
Hi,

Regarding fixing it fully, we have the official response.... We analysed it and concluded that it is correct.

There is a problem regarding making this feature. It seems possible to use a virtual generated column as a fine workaround for this:

CREATE TABLE pt3 (
  id int NOT NULL,
  created_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  x TIMESTAMP AS (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
PARTITION BY RANGE (UNIX_TIMESTAMP(x)) (
PARTITION p202306 VALUES LESS THAN (UNIX_TIMESTAMP('2023-07-01')),
PARTITION p202307 VALUES LESS THAN (UNIX_TIMESTAMP('2023-08-01')),
PARTITION p202308 VALUES LESS THAN (UNIX_TIMESTAMP('2023-09-01')),
PARTITION p3 VALUES LESS THAN (MAXVALUE)
);

INSERT INTO pt3(id, created_at) values
  (1, '2023-06-01 08:30:00'), (2, '2023-07-01 08:30:00'),
  (3, '2023-08-01 08:30:00'), (4, '2023-09-01 08:30:00');

Then query on the generated column:

explain SELECT * FROM pt3 WHERE x < '2023-06-01 08:30:00';

One possible problem that we see it that querying on exact TIMESTAMP values will not do partition pruning.

One may also have to enable SQL mode TIME_TRUNCATE_FRACTIONAL to avoid rounding for the generated column.

That is a final conclusion.