| Bug #120437 | information_schema.partitions returns UINT64_MAX (18446744073709551615) for DATA_LENGTH of partition dropped during conc | ||
|---|---|---|---|
| Submitted: | 11 May 8:47 | Modified: | 11 May 8:57 |
| Reporter: | Yakir Gibraltar (OCA) | Email Updates: | |
| Status: | Open | Impact on me: | |
| Category: | MySQL Server: Information schema | Severity: | S2 (Serious) |
| Version: | 8.0.35, 8.4.8, 9.3.0, 9.7.0 | OS: | Any |
| Assigned to: | CPU Architecture: | Any | |
[11 May 8:49]
Yakir Gibraltar
repreduce file
Attachment: repro_partition_ullong.sh (text/x-sh), 7.12 KiB.
[11 May 8:50]
Yakir Gibraltar
You can easy reproduce with the attached script:
➜ ~ bash /Users/yakir.g/git/percona-server/repro_partition_ullong.sh --docker mysql:9.7
==> Pulling mysql:9.7 ...
9.7: Pulling from library/mysql
Digest: sha256:f0ef1d92fa650fcfa5b85f1d82bb1a56a6dd579bf256b8f8f2a5a0b1b61c8b0b
Status: Image is up to date for mysql:9.7
docker.io/library/mysql:9.7
==> Starting container ...
3dd00ff0a06dc6817622ccb76a4f6e708db48f6c5468830ff7fe534d283b70d0
==> Waiting for mysqld ...... ready.
==> Server version:
+-----------+-------------------------+
| VERSION() | @@transaction_isolation |
+-----------+-------------------------+
| 9.7.0 | REPEATABLE-READ |
+-----------+-------------------------+
==> Creating partitioned table ...
Table ready with partitions p0/p1/p2/p3.
==> Running two-session REPEATABLE READ race ...
Session A: lock RR snapshot (p1 visible) → SLEEP(5s) → query I_S.PARTITIONS
Session B: DROP PARTITION p1 while Session A sleeps
Session A sleeping — dropping p1 now ...
p1 DROPPED (committed). Waiting for Session A to finish ...
==> Session A I_S.PARTITIONS output:
------------------------------------------------------------
is_query PARTITION_NAME DATA_LENGTH INDEX_LENGTH
is_query p0 16384 0
is_query p1 18446744073709551615 0
is_query p2 16384 0
is_query p3 16384 0
------------------------------------------------------------
Thank you, Yakir Gibraltar.
[11 May 8:57]
Yakir Gibraltar
Add option to edit a comment..

Description: When a partition is dropped while an information_schema.partitions query is executing, the DATA_LENGTH column returns 18446744073709551615 (= (ulonglong)-1 = UINT64_MAX = 2^64-1) instead of NULL for that partition row. Confirmed affected: 8.0.35, 8.4.8, 9.3.0, 9.7.0 (all versions tested). --- Observed symptom --- In production monitoring queries such as: SELECT partition_name, (data_length + index_length) / 1024 / 1024 / 1024 AS size_gb FROM information_schema.partitions WHERE table_schema = 'mydb'; the size_gb column shows 17179869183.999999999069 (~17 GB) for a recently-dropped partition, triggering false disk-space alerts. --- Root cause --- Item_func_internal_data_length::val_int() and Item_func_internal_index_length::val_int() in sql/item_func.cc call get_table_statistics() and return its raw result without checking for the (ulonglong)-1 error sentinel. When the partition does not exist (ER_UNKNOWN_PARTITION is raised), get_table_statistics() returns (ulonglong)-1 but does NOT set null_value=true, and neither does the caller — so raw UINT64_MAX leaks into the result set. Item_func_internal_data_free::val_int() has the correct guard: if (null_value == false && result == (ulonglong)-1) null_value = true; but data_length and index_length are both missing it. --- Race mechanism --- The outer I_S scan reads mysql.table_partitions rows using a REPEATABLE READ snapshot. For each row, INTERNAL_DATA_LENGTH() is evaluated via an attachable transaction that opens a NEWER snapshot — one where the partition may already have been dropped and committed. This triggers ER_UNKNOWN_PARTITION in the storage layer, which returns (ulonglong)-1 without setting null_value=true. How to repeat: Guaranteed reproduction using the REPEATABLE READ. -- Setup (run once) CREATE TABLE parts ( id INT NOT NULL, d DATE NOT NULL, PRIMARY KEY (id, d) ) PARTITION BY RANGE (TO_DAYS(d)) ( PARTITION p0 VALUES LESS THAN (TO_DAYS('2024-01-01')), PARTITION p1 VALUES LESS THAN (TO_DAYS('2024-02-01')), PARTITION p2 VALUES LESS THAN (TO_DAYS('2024-03-01')), PARTITION p3 VALUES LESS THAN (MAXVALUE) ); INSERT INTO parts VALUES (1,'2023-12-15'),(2,'2024-01-15'),(3,'2024-02-15'),(4,'2024-03-15'); -- Session A: lock RR snapshot showing p1, then sleep SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; START TRANSACTION; SELECT partition_name, data_length FROM information_schema.partitions WHERE table_schema = DATABASE() AND table_name = 'parts' AND partition_name = 'p1'; SELECT SLEEP(10); -- Session B drops p1 during this sleep SELECT partition_name, data_length, index_length FROM information_schema.partitions WHERE table_schema = DATABASE() AND table_name = 'parts'; ROLLBACK; -- Session B: run while Session A is inside SLEEP(10) ALTER TABLE parts DROP PARTITION p1; -- Expected result (after fix): -- p1 row: DATA_LENGTH = NULL, INDEX_LENGTH = NULL -- Actual result (bug): -- p1 row: DATA_LENGTH = 18446744073709551615, INDEX_LENGTH = 0 A self-contained bash repro script (docker + mysql client only) is attached to this report. Suggested fix: Add the missing null_value guard to both functions in sql/item_func.cc. The identical guard already exists in Item_func_internal_data_free::val_int(). --- sql/item_func.cc --- longlong Item_func_internal_data_length::val_int() { DBUG_TRACE; const ulonglong result = get_table_statistics( args, arg_count, dd::info_schema::enum_table_stats_type::DATA_LENGTH, &null_value); if (null_value == false && result == (ulonglong)-1) null_value = true; // ADD return result; } longlong Item_func_internal_index_length::val_int() { DBUG_TRACE; const ulonglong result = get_table_statistics( args, arg_count, dd::info_schema::enum_table_stats_type::INDEX_LENGTH, &null_value); if (null_value == false && result == (ulonglong)-1) null_value = true; // ADD return result; } After the fix, DATA_LENGTH and INDEX_LENGTH will correctly return NULL for partitions dropped during the I_S scan, preventing UINT64_MAX from leaking into monitoring queries.