Introduction
As the current mainstream version, MySQL 8.0 has made numerous improvements in performance optimization. According to official release records, from version 8.0.0 to the latest 8.0.41, 20 out of 42 releases have included optimizer enhancements. Over multiple installments, I will list optimizer-related content from MySQL 8.0 Release Notes and, through testing, visually guide everyone to understand the improvements made by MySQL. For database developers and administrators, deeply understanding these optimization features is crucial for enhancing application performance. To this end, I have specially curated the “In-Depth Analysis of MySQL 8.0 Optimizer” series of articles, which will systematically interpret these improvements. This series will be structured into the following topics:
- Logical Optimization of SQL Statements (Current Installment): Focusing on query rewriting-level optimizations
- Index Optimization and Utilization: Exploring index-related improvements and new features
- Execution Plans and Cost Models: Analyzing the evolution of the optimizer’s decision-making mechanisms
The versions involved in the comparative tests in this article are MySQL 8.0.41 and 5.7.39 deployed via Docker. Table creation statements and data generation scripts can be found in the Appendix.
Conclusion
This article will focus on optimizations related to the logical rewriting of SQL statements. By conducting practical tests to compare differences between MySQL 8.0 and 5.7, we aim to help developers better understand and leverage these optimization features. To visually demonstrate their performance advantages, we will perform comparative analyses through practical tests against MySQL 5.7 across the following three core optimization features. Below is a summary of key information:
1. GROUP BY Now Defaults to No Sorting
Introduced in version: 8.0.1
- In MySQL 8.0,
GROUP BY
no longer implicitly sorts results when no explicit ASC
/DESC
is specified. This may cause query results to differ from earlier MySQL versions. - In MySQL 5.7, adding
ORDER BY NULL
could avoid filesort operations in execution plans. - Adding indexes to
GROUP BY
columns effectively prevents temporary tables and filesorts.
2. Derived Condition Pushdown Optimization
Introduced in version: 8.0.22, with enhancements in 8.0.28, 8.0.29, and 8.0.41. Reference
- Condition pushdown significantly improves performance for complex, non-standard SQL queries in MySQL 8.0+.
- While MySQL optimizes condition pushdown automatically, writing well-structured SQL is still recommended. SQLFlash also supports this optimization.
3. Subquery to Derived Table Conversion
Introduced in version: 8.0.21, with GROUP BY
support added in 8.0.22.
- This feature is not enabled by default; activate it using:
SET @@optimizer_switch='subquery_to_derived=on';
- Official documentation states this optimization rarely improves performance and may even degrade it in many cases. It is primarily for testing.
- The parameter is recommended to remain disabled due to limited applicability (e.g., it does not support converting
ANY
/ALL
subqueries to MAX()
/MIN()
). SQLFlash offers alternative optimizations for such scenarios: Blog Link
I. GROUP BY Now Defaults to No Sorting
Starting from version 8.0.22, MySQL has modified the default behavior of GROUP BY
. In MySQL 8.0, when no explicit ASC
/DESC
is specified for GROUP BY
columns, results are no longer implicitly sorted by default. This reduces file sorting operations compared to MySQL 5.7, significantly improving performance, but query results may differ from earlier versions.
Why Did MySQL 5.7 Default to Sorting?
This was an implementation “side effect” of early MySQL versions. One standard approach to implement GROUP BY
involves first sorting the grouping columns, then iterating through the sorted results to aggregate values. Sorting naturally clusters identical values, simplifying the grouping process. MySQL 5.7 and earlier retained this design, making GROUP BY
results appear inherently ordered.
Why Did MySQL 8.0 Remove Implicit Sorting?
SQL Standard Compliance:
The SQL standard does not mandate ordered results for GROUP BY
. Explicit ORDER BY
should be used for ordering requirements. Removing implicit sorting aligns MySQL with SQL standards.
Performance Optimization:
Sorting is resource-intensive, especially for large datasets requiring disk-based filesort operations. However, many applications do not require ordered GROUP BY
results, making this default behavior an unnecessary performance overhead.
Developer Control:
MySQL 8.0 shifts control to developers: use ORDER BY
for ordered results or omit it to leverage performance gains. This design emphasizes explicit intent and efficiency.
Testing Process
When comparing the execution plans of MySQL 8.0 and 5.7, the 8.0 execution plan shows one less “Using filesort” operation. From the result set analysis, MySQL 5.7 implicitly performed an additional sorting step by default.
8.0.41
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| mysql> explain select student_name from students group by student_name; -- 8.0.41
+----+-------------+----------+------------+------+---------------+------+---------+------+-------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+------+---------------+------+---------+------+-------+----------+-----------------+
| 1 | SIMPLE | students | NULL | ALL | NULL | NULL | NULL | NULL | 99746 | 100.00 | Using temporary |
+----+-------------+----------+------------+------+---------------+------+---------+------+-------+----------+-----------------+
mysql> select * from (select student_name from students group by student_name) as t limit 3; -- 8.0.41
+--------------+
| student_name |
+--------------+
| Student_1 |
| Student_2 |
| Student_3 |
+--------------+
3 rows in set (0.16 sec)
|
5.7.39
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| mysql> explain select student_name from students group by student_name; -- 5.7.39
+----+-------------+----------+------------+------+---------------+------+---------+------+--------+----------+---------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+------+---------------+------+---------+------+--------+----------+---------------------------------+
| 1 | SIMPLE | students | NULL | ALL | NULL | NULL | NULL | NULL | 100066 | 100.00 | Using temporary; Using filesort |
+----+-------------+----------+------------+------+---------------+------+---------+------+--------+----------+---------------------------------+
mysql> select * from (select student_name from students group by student_name) as t limit 3; -- 5.7.39
+--------------+
| student_name |
+--------------+
| Student_1 |
| Student_10 |
| Student_100 |
+--------------+
3 rows in set (1.76 sec)
|
With indexes in place, the execution plans of both versions show no differences.​​
8.0.41
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| explain select student_name from students group by student_name; -- 8.0.41
+----+-------------+----------+------------+-------+---------------+-------+---------+------+-------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+---------------+-------+---------+------+-------+----------+-------------+
| 1 | SIMPLE | students | NULL | index | idx_1 | idx_1 | 1023 | NULL | 99746 | 100.00 | Using index |
+----+-------------+----------+------------+-------+---------------+-------+---------+------+-------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> select * from (select student_name from students group by student_name order by null) as t limit 3;
+--------------+
| student_name |
+--------------+
| Student_1 |
| Student_10 |
| Student_100 |
+--------------+
3 rows in set (0.08 sec)
|
5.7.39
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| mysql> explain select student_name from students group by student_name; -- 5.7.39
+----+-------------+----------+------------+-------+---------------+-------+---------+------+-------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+---------------+-------+---------+------+-------+----------+-------------+
| 1 | SIMPLE | students | NULL | index | idx_1 | idx_1 | 1023 | NULL | 99881 | 100.00 | Using index |
+----+-------------+----------+------------+-------+---------------+-------+---------+------+-------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> select * from (select student_name from students group by student_name) as t limit 3; -- 5.7.39
+--------------+
| student_name |
+--------------+
| Student_1 |
| Student_10 |
| Student_100 |
+--------------+
3 rows in set (0.13 sec)
|
This explains why MySQL 5.7 introduced a rather “quirky” SQL optimization trick: explicitly adding ORDER BY NULL to reduce sorting overhead (when order is irrelevant). In my test environment, this reduced execution time by over 50% — from ​1.76 seconds to 0.56 seconds.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| mysql> explain select student_name from students group by student_name order by null; -- 5.7.39
+----+-------------+----------+------------+------+---------------+------+---------+------+--------+----------+-----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+------+---------------+------+---------+------+--------+----------+-----------------+
| 1 | SIMPLE | students | NULL | ALL | NULL | NULL | NULL | NULL | 100066 | 100.00 | Using temporary |
+----+-------------+----------+------------+------+---------------+------+---------+------+--------+----------+-----------------+
mysql> select * from (select student_name from students group by student_name order by null) as t limit 3;
+--------------+
| student_name |
+--------------+
| Student_1 |
| Student_2 |
| Student_3 |
+--------------+
3 rows in set (0.56 sec)
|
II. Derived Condition Pushdown Optimization
Before MySQL 8.0.22, when a derived table was materialized but not merged, MySQL would fully materialize the entire table and then apply the WHERE
conditions to filter all rows. This approach severely degraded performance. MySQL now supports this optimization.
What is Derived Table Condition Pushdown?
The essence of “pushdown” is “early filtering”—a golden rule of database optimization. The optimizer determines that pushing external WHERE
conditions into the subquery is logically equivalent. When the filtering rate is high, this significantly reduces the amount of data the subquery needs to process and return, yielding notable performance improvements. For example, if the external WHERE
condition is highly selective (e.g., WHERE id = 1
), and the subquery itself returns massive data (e.g., one million rows), the database would first laboriously generate a one-million-row temporary table, only to extract that single row. This causes significant I/O, CPU, and memory waste.
However, condition pushdown has strict constraints and requires logical equivalence. Take LIMIT
as an example: If the derived table is (SELECT ... ORDER BY score DESC LIMIT 10)
(intended to select the top 10 highest-scoring records), pushing the external WHERE user_type = 'VIP'
condition into the subquery might cause the subquery to select the top 10 highest-scoring VIP records. This deviates entirely from the original semantics—“select the top 10 highest-scoring records across all users, then check how many are VIPs”—leading to incorrect results. Thus, the optimizer disables this optimization in such scenarios to ensure query correctness.
MySQL’s Support for Pushdown
Non-Derived Tables
For non-derived tables, MySQL performs subquery elimination, so the optimizer doesn’t need to push down conditions. There is no difference between MySQL 8.0 and 5.7 in this scenario.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| mysql> explain select * from (select * from students) as t1 where student_name = "Student_97";
+----+-------------+----------+------------+------+------------------+------------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+------+------------------+------------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | students | NULL | ref | idx_student_name | idx_student_name | 1023 | const | 1 | 100.00 | NULL |
+----+-------------+----------+------------+------+------------------+------------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from (select * from students where student_name = "Student_97") as t1;
+----+-------------+----------+------------+------+------------------+------------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+------+------------------+------------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | students | NULL | ref | idx_student_name | idx_student_name | 1023 | const | 1 | 100.00 | NULL |
+----+-------------+----------+------------+------+------------------+------------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.01 sec)
|
Simple Scenarios
SELECT * FROM (SELECT f1, f2 FROM t1) AS dt WHERE f1 < 3 AND f2 > 11;
Equivalent to
SELECT * FROM (SELECT f1, f2 FROM t1 WHERE f1 < 3 AND f2 > 11) AS dt;
Aggregation Scenarios
(1) When the column in the external WHERE condition is a GROUP BY column, it can be directly pushed down into the WHERE clause.
SELECT * FROM (SELECT i,j, SUM(k) AS sum FROM t1 GROUP BY i,j) AS dt WHERE i > 10;
Equivalent to
SELECT * FROM (SELECT i,j, SUM(k) AS sum FROM t1 WHERE i > 10 GROUP BY i,j) AS dt;
(2) When the external WHERE condition involves aggregate fields, the WHERE can be pushed down and converted to HAVING
SELECT * FROM (SELECT i, j, SUM(k) AS sum FROM t1 GROUP BY i, j) AS dt WHERE sum > 100;
Equivalent to
SELECT * FROM (SELECT i, j, SUM(k) AS sum FROM t1 GROUP BY i, j HAVING sum > 100) AS dt;
UNION Scenarios
1
2
3
4
5
6
| SELECT * FROM (
SELECT id, c1 FROM t1
UNION ALL
SELECT id, c1 FROM t2
) AS dt
WHERE dt.c1 = 12
|
Equivalent to
1
2
3
4
5
| SELECT * FROM (
SELECT id, c1 FROM t1 WHERE c1 = 12
UNION ALL
SELECT id, c1 FROM t2 WHERE c1 = 12
) AS dt
|
Unsupported Scenarios
MySQL’s official documentation lists numerous cases where pushdown is not supported. Some scenarios cannot be rewritten due to inherent logical non-equivalence, such as:
- Derived tables cannot use LIMIT clauses
- Conditions containing subqueries cannot be pushed down
- This optimization cannot be used when the derived table is the inner table of an outer join
- SELECT columns contain variable assignments
Other cases are abandoned due to cost considerations and high code modification complexity, even though logical equivalence might be possible, such as:
- CTEs (Common Table Expressions) that are referenced multiple times
Testing Process
8.0.41
1
2
3
4
5
| select * from (
select student_name from students group by student_name
) as t
where
t.student_name ="Student_97";
|
5.7.39
1
2
3
4
| select * from (
select student_name from students group by student_name
where student_name ="Student_97"
) as t;
|
On MySQL 8.0, there is no difference in execution between the two SQL statements, and the actual execution plans are also identical.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| mysql> select * from (select student_name from students group by student_name) as t where t.student_name ="Student_97";
+--------------+
| student_name |
+--------------+
| Student_97 |
+--------------+
1 row in set (0.00 sec)
mysql> select * from (select student_name from students where student_name = "Student_97" group by student_name) as t;
+--------------+
| student_name |
+--------------+
| Student_97 |
+--------------+
1 row in set (0.00 sec)
|
On MySQL 5.7, when the condition is not pushed down, the derived table performs a full aggregation, showing a noticeable performance difference compared to manual pushdown.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| mysql> select * from (select student_name from students group by student_name) as t where t.student_name ="Student_97";
+--------------+
| student_name |
+--------------+
| Student_97 |
+--------------+
1 row in set (0.15 sec)
mysql> select * from (select student_name from students where student_name = "Student_97" group by student_name) as t;
+--------------+
| student_name |
+--------------+
| Student_97 |
+--------------+
1 row in set (0.00 sec)
|
Comparing the execution plans of MySQL 8.0 and 5.7, the condition has been pushed down into the derived table.
8.0.41
1
2
3
4
5
6
7
8
| mysql> explain select * from (select student_name from students group by student_name) as t where t.student_name = "Student_97"; -- 8.0.41
+----+-------------+------------+------------+--------+------------------+------------------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+--------+------------------+------------------+---------+-------+------+----------+-------------+
| 1 | PRIMARY | <derived2> | NULL | system | NULL | NULL | NULL | NULL | 1 | 100.00 | NULL |
| 2 | DERIVED | students | NULL | ref | idx_student_name | idx_student_name | 1023 | const | 1 | 100.00 | Using index |
+----+-------------+------------+------------+--------+------------------+------------------+---------+-------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
|
5.7.39
1
2
3
4
5
6
7
| mysql> explain select * from (select student_name from students group by student_name) as t where t.student_name = "Student_97"; -- 5.7.39
+----+-------------+------------+------------+-------+---------------+-------+---------+------+-------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+------------+------------+-------+---------------+-------+---------+------+-------+----------+-------------+
| 1 | PRIMARY | <derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 99881 | 10.00 | Using where |
| 2 | DERIVED | students | NULL | index | idx_1 | idx_1 | 1023 | NULL | 99881 | 100.00 | Using index |
+----+-------------+------------+------------+-------+---------------+-------+---------+------+-------+----------+-------------+
|
III. Subquery to Derived Table Optimization**
This optimization converts scalar subqueries in SELECT, WHERE, JOIN, or HAVING clauses into left outer joins with derived tables. For example:
1
| SELECT * FROM t1 WHERE t1.b < 0 OR t1.a IN (SELECT t2.a + 1 FROM t2);
|
can be rewritten as:
1
2
3
4
5
6
| SELECT * FROM t1
LEFT JOIN (SELECT DISTINCT a + 1 AS e FROM t2) d
ON t1.a = d.e
WHERE t1.b < 0
OR
d.e IS NOT NULL;
|
Why Convert Subqueries to Joins?
In many database systems, subqueries—especially correlated subqueries—perform poorly. The traditional execution method might involve “nested loops” (executing the subquery once for each row of the outer query). If the outer query has N rows, the subquery runs N times, a process known as RBAR (Row-By-Agonizing-Row). Converting subqueries to joins shifts execution from “nested loops” to the optimizer’s more powerful “join processor.” The optimizer can then freely choose better join algorithms based on statistics, indexes, and other factors, theoretically providing greater optimization potential and potentially finding a much better execution plan than the original subquery.
For example:
- Hash Join: Often far more efficient than nested loops when join fields lack indexes.
- Outer Join to Inner Join Conversion.
- Join Reordering: The optimizer can adjust table join order, using smaller result sets to drive the join.
Why Is It Disabled by Default (and Might Even Slow Queries Down)?
Challenges with the Optimizer’s Cost Model:
The rewritten query structure becomes more complex. The optimizer must evaluate the execution cost of the new query (in join form), but its cost estimation model isn’t always accurate. Sometimes it might “mistakenly” assume the join costs less, but in reality, overheads like derived table materialization or hash table construction lead to longer execution times.
Not All Subqueries Are Inefficient:
If a subquery can be “de-correlated” by the optimizer or efficiently use indexes, it might already execute quickly. Forcing a conversion to a join in such cases adds unnecessary overhead.
Limited Applicability:
As noted, this optimization supports only limited scenarios. For many common, painful subquery cases (like IN / ANY / ALL), MySQL’s support is limited, and more mature, reliable optimization strategies exist elsewhere.
Thus, the MySQL team made the prudent and reasonable decision to offer this as an “optional” switch that requires user evaluation—rather than a universal default optimization.
Testing Process
Test before and after enabling the optimizer parameter. The execution plans for the same SQL differ, but there is little difference in performance.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| mysql> select * from students where students.student_id = (select max(student_id) from enrollments where enrollment_id > 100);
+------------+----------------+------+--------+---------------------+
| student_id | student_name | age | gender | created_at |
+------------+----------------+------+--------+---------------------+
| 100000 | Student_100000 | 22 | F | 2025-08-11 03:06:57 |
+------------+----------------+------+--------+---------------------+
1 row in set (0.22 sec)
mysql> explain select * from students where students.student_id = (select max(student_id) from enrollments where enrollment_id > 100);
+----+-------------+-------------+------------+-------+------------------------+---------+---------+-------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------------+------------+-------+------------------------+---------+---------+-------+--------+----------+-------------+
| 1 | PRIMARY | students | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
| 2 | SUBQUERY | enrollments | NULL | range | PRIMARY,idx_student_id | PRIMARY | 4 | NULL | 249600 | 100.00 | Using where |
+----+-------------+-------------+------------+-------+------------------------+---------+---------+-------+--------+----------+-------------+
2 rows in set, 1 warning (0.21 sec)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| mysql> SET @@optimizer_switch='subquery_to_derived=on';
Query OK, 0 rows affected (0.00 sec)
mysql> select * from students where students.student_id = (select max(student_id) from enrollments where enrollment_id > 100);
+------------+----------------+------+--------+---------------------+
| student_id | student_name | age | gender | created_at |
+------------+----------------+------+--------+---------------------+
| 100000 | Student_100000 | 22 | F | 2025-08-11 03:06:57 |
+------------+----------------+------+--------+---------------------+
1 row in set (0.22 sec)
mysql> explain select * from students where students.student_id = (select max(student_id) from enrollments where enrollment_id > 100);
+----+-------------+-------------+------------+--------+------------------------+---------+---------+-----------------------------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------------+------------+--------+------------------------+---------+---------+-----------------------------+--------+----------+-------------+
| 1 | PRIMARY | <derived2> | NULL | ALL | NULL | NULL | NULL | NULL | 1 | 100.00 | Using where |
| 1 | PRIMARY | students | NULL | eq_ref | PRIMARY | PRIMARY | 4 | derived_1_2.max(student_id) | 1 | 100.00 | NULL |
| 2 | DERIVED | enrollments | NULL | range | PRIMARY,idx_student_id | PRIMARY | 4 | NULL | 249600 | 100.00 | Using where |
+----+-------------+-------------+------------+--------+------------------------+---------+---------+-----------------------------+--------+----------+-------------+
3 rows in set, 1 warning (0.27 sec)
|
References
Appendix
Table Creation Statements
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| DROP TABLE IF EXISTS students;
CREATE TABLE students (
student_id INT PRIMARY KEY AUTO_INCREMENT,
student_name VARCHAR(100) NOT NULL,
age INT,
gender ENUM('M','F') DEFAULT 'M',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
DROP TABLE IF EXISTS enrollments;
CREATE TABLE enrollments (
enrollment_id INT PRIMARY KEY AUTO_INCREMENT,
student_id INT NOT NULL,
course_id INT NOT NULL,
enrolled_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (student_id) REFERENCES students(student_id)
) ENGINE=InnoDB;
|
Data Generation Script
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| DELIMITER $$
DROP PROCEDURE IF EXISTS generate_test_data $$
CREATE PROCEDURE generate_test_data(IN student_count INT, IN enrollment_per_student INT)
BEGIN
DECLARE i INT DEFAULT 1;
DECLARE j INT;
-- 插入 students 数据
WHILE i <= student_count DO
INSERT INTO students (student_name, age, gender)
VALUES (CONCAT('Student_', i), FLOOR(18 + RAND()*5), IF(RAND() > 0.5, 'M', 'F'));
SET i = i + 1;
END WHILE;
-- 插入 enrollments 数据
SET i = 1;
WHILE i <= student_count DO
SET j = 1;
WHILE j <= enrollment_per_student DO
INSERT INTO enrollments (student_id, course_id)
VALUES (i, FLOOR(1 + RAND()*50));
SET j = j + 1;
END WHILE;
SET i = i + 1;
END WHILE;
END $$
DELIMITER ;
CALL generate_test_data(100000, 5);
|