Description:
The optimizer incorrectly removes the outer ORDER BY clause when GROUP BY is present, relying on the temporary table's auto-generated index (<auto_key0>) to provide ordering. However, when internal_tmp_mem_storage_engine = TempTable, the execution plan uses access_type: ALL (full table scan) while index_provides_order is still true. A full table scan on a TempTable does not guarantee index-ordered output, causing wrong results.
With MEMORY engine, access_type: index is chosen, which correctly preserves order.
How to repeat:
```sql
SET SESSION internal_tmp_mem_storage_engine = TempTable;
CREATE DATABASE IF NOT EXISTS test_db;
USE test_db;
CREATE TABLE profit_table (
period_code VARCHAR(7) NOT NULL,
consol_org VARCHAR(64) NOT NULL,
ytenant_id VARCHAR(32) NOT NULL,
net_profit_ptd DECIMAL(18,2) DEFAULT 0,
net_profit_ytd DECIMAL(18,2) DEFAULT 0
);
CREATE TABLE cash_table (
period_code VARCHAR(7) NOT NULL,
consol_org VARCHAR(64) NOT NULL,
ytenant_id VARCHAR(32) NOT NULL,
net_cash_flow_oper DECIMAL(18,2) DEFAULT 0,
net_cash_flow_oper_ytd DECIMAL(18,2) DEFAULT 0
);
INSERT INTO profit_table VALUES
('2023-12', 'org001', 'tenant001', 100, 1200),
('2024-12', 'org001', 'tenant001', 150, 1800),
('2025-12', 'org001', 'tenant001', 200, 2400),
('2026-06', 'org001', 'tenant001', 120, 3600);
INSERT INTO cash_table VALUES
('2023-12', 'org001', 'tenant001', 80, 900),
('2024-12', 'org001', 'tenant001', 120, 1500),
('2025-12', 'org001', 'tenant001', 160, 2100),
('2026-06', 'org001', 'tenant001', 100, 3000);
SELECT ipo.period_code AS u_period_code, SUM(nprofit) AS total
FROM (
SELECT po.period_code AS period_code, IFNULL(pd.net_profit, 0) / 1 AS nprofit
FROM (
WITH period_offsets AS (
SELECT '2024-06' AS period_code, '2026-06' AS period_0
UNION ALL SELECT '2025-12', '2025-12'
UNION ALL SELECT '2024-12', '2024-12'
UNION ALL SELECT '2023-12', '2023-12'
),
profit_base AS (
SELECT period_code, SUM(net_profit_ytd) AS net_profit_ytd
FROM profit_table
WHERE period_code BETWEEN '2023-01' AND '2026-06'
AND consol_org = 'org001'
AND ytenant_id = 'tenant001'
GROUP BY period_code
),
cash_base AS (
SELECT period_code, SUM(net_cash_flow_oper_ytd) AS ocf_ytd
FROM cash_table
WHERE period_code BETWEEN '2023-01' AND '2026-06'
AND consol_org = 'org001'
AND ytenant_id = 'tenant001'
GROUP BY period_code
),
base_aggregated AS (
SELECT po.period_code,
IFNULL(p.net_profit_ytd, 0) * 12 / NULLIF(CAST(SUBSTRING(po.period_code, 6, 2) AS DECIMAL), 0) AS net_profit,
IFNULL(c.ocf_ytd, 0) * 12 / NULLIF(CAST(SUBSTRING(po.period_code, 6, 2) AS DECIMAL), 0) AS ocf
FROM period_offsets po
LEFT JOIN profit_base p ON p.period_code = po.period_0
LEFT JOIN cash_base c ON c.period_code = po.period_0
)
SELECT po.period_code, IFNULL(pd.net_profit, 0) / 1 AS nprofit
FROM period_offsets po
LEFT JOIN base_aggregated pd ON pd.period_code = po.period_code
ORDER BY po.period_code
) inner_q
) ipo
GROUP BY ipo.period_code
ORDER BY u_period_code ASC;
```
**Actual result (TempTable):**
```
2024-06 -- wrong order (insertion order from period_offsets)
2025-12
2024-12
2023-12
```
**Expected result (same as MEMORY engine):**
```
2023-12
2024-06
2024-12
2025-12
```
**Workaround:** SET SESSION internal_tmp_mem_storage_engine = MEMORY;
---
### Root Cause (from OPTIMIZER TRACE)
**TempTable engine:**
```json
"optimizing_distinct_group_by_order_by": {
"removed_order_by": true
},
"reconsidering_access_paths_for_index_ordering": {
"clause": "GROUP BY",
"index_order_summary": {
"index": "<auto_key0>",
"access_type": "ALL",
"index_provides_order": true,
"plan_changed": true
}
}
```
**MEMORY engine:**
```json
"reconsidering_access_paths_for_index_ordering": {
"index_order_summary": {
"index": "<auto_key0>",
"access_type": "index",
"index_provides_order": true,
"plan_changed": true
}
}
```
The optimizer removes ORDER BY because it believes the <auto_key0> index on the TempTable provides ordering. However, when access_type: ALL is chosen for TempTable, the full table scan returns rows in insertion order (from the UNION ALL CTE), not in index order. This is a mismatch between the optimizer's assumption and the storage engine's behavior.
---
Suggested fix:
Prevent index_provides_order from being true when access_type: ALL on TempTable, or force filesort in join_execution when the access method cannot guarantee index ordering.
Description: The optimizer incorrectly removes the outer ORDER BY clause when GROUP BY is present, relying on the temporary table's auto-generated index (<auto_key0>) to provide ordering. However, when internal_tmp_mem_storage_engine = TempTable, the execution plan uses access_type: ALL (full table scan) while index_provides_order is still true. A full table scan on a TempTable does not guarantee index-ordered output, causing wrong results. With MEMORY engine, access_type: index is chosen, which correctly preserves order. How to repeat: ```sql SET SESSION internal_tmp_mem_storage_engine = TempTable; CREATE DATABASE IF NOT EXISTS test_db; USE test_db; CREATE TABLE profit_table ( period_code VARCHAR(7) NOT NULL, consol_org VARCHAR(64) NOT NULL, ytenant_id VARCHAR(32) NOT NULL, net_profit_ptd DECIMAL(18,2) DEFAULT 0, net_profit_ytd DECIMAL(18,2) DEFAULT 0 ); CREATE TABLE cash_table ( period_code VARCHAR(7) NOT NULL, consol_org VARCHAR(64) NOT NULL, ytenant_id VARCHAR(32) NOT NULL, net_cash_flow_oper DECIMAL(18,2) DEFAULT 0, net_cash_flow_oper_ytd DECIMAL(18,2) DEFAULT 0 ); INSERT INTO profit_table VALUES ('2023-12', 'org001', 'tenant001', 100, 1200), ('2024-12', 'org001', 'tenant001', 150, 1800), ('2025-12', 'org001', 'tenant001', 200, 2400), ('2026-06', 'org001', 'tenant001', 120, 3600); INSERT INTO cash_table VALUES ('2023-12', 'org001', 'tenant001', 80, 900), ('2024-12', 'org001', 'tenant001', 120, 1500), ('2025-12', 'org001', 'tenant001', 160, 2100), ('2026-06', 'org001', 'tenant001', 100, 3000); SELECT ipo.period_code AS u_period_code, SUM(nprofit) AS total FROM ( SELECT po.period_code AS period_code, IFNULL(pd.net_profit, 0) / 1 AS nprofit FROM ( WITH period_offsets AS ( SELECT '2024-06' AS period_code, '2026-06' AS period_0 UNION ALL SELECT '2025-12', '2025-12' UNION ALL SELECT '2024-12', '2024-12' UNION ALL SELECT '2023-12', '2023-12' ), profit_base AS ( SELECT period_code, SUM(net_profit_ytd) AS net_profit_ytd FROM profit_table WHERE period_code BETWEEN '2023-01' AND '2026-06' AND consol_org = 'org001' AND ytenant_id = 'tenant001' GROUP BY period_code ), cash_base AS ( SELECT period_code, SUM(net_cash_flow_oper_ytd) AS ocf_ytd FROM cash_table WHERE period_code BETWEEN '2023-01' AND '2026-06' AND consol_org = 'org001' AND ytenant_id = 'tenant001' GROUP BY period_code ), base_aggregated AS ( SELECT po.period_code, IFNULL(p.net_profit_ytd, 0) * 12 / NULLIF(CAST(SUBSTRING(po.period_code, 6, 2) AS DECIMAL), 0) AS net_profit, IFNULL(c.ocf_ytd, 0) * 12 / NULLIF(CAST(SUBSTRING(po.period_code, 6, 2) AS DECIMAL), 0) AS ocf FROM period_offsets po LEFT JOIN profit_base p ON p.period_code = po.period_0 LEFT JOIN cash_base c ON c.period_code = po.period_0 ) SELECT po.period_code, IFNULL(pd.net_profit, 0) / 1 AS nprofit FROM period_offsets po LEFT JOIN base_aggregated pd ON pd.period_code = po.period_code ORDER BY po.period_code ) inner_q ) ipo GROUP BY ipo.period_code ORDER BY u_period_code ASC; ``` **Actual result (TempTable):** ``` 2024-06 -- wrong order (insertion order from period_offsets) 2025-12 2024-12 2023-12 ``` **Expected result (same as MEMORY engine):** ``` 2023-12 2024-06 2024-12 2025-12 ``` **Workaround:** SET SESSION internal_tmp_mem_storage_engine = MEMORY; --- ### Root Cause (from OPTIMIZER TRACE) **TempTable engine:** ```json "optimizing_distinct_group_by_order_by": { "removed_order_by": true }, "reconsidering_access_paths_for_index_ordering": { "clause": "GROUP BY", "index_order_summary": { "index": "<auto_key0>", "access_type": "ALL", "index_provides_order": true, "plan_changed": true } } ``` **MEMORY engine:** ```json "reconsidering_access_paths_for_index_ordering": { "index_order_summary": { "index": "<auto_key0>", "access_type": "index", "index_provides_order": true, "plan_changed": true } } ``` The optimizer removes ORDER BY because it believes the <auto_key0> index on the TempTable provides ordering. However, when access_type: ALL is chosen for TempTable, the full table scan returns rows in insertion order (from the UNION ALL CTE), not in index order. This is a mismatch between the optimizer's assumption and the storage engine's behavior. --- Suggested fix: Prevent index_provides_order from being true when access_type: ALL on TempTable, or force filesort in join_execution when the access method cannot guarantee index ordering.