Lesson 19 of the SQL Optimization Course: Mastering MySQL Functional Indexes | SQLFlash

Introduction

For relational databases, the design of tables and SQL is written are particularly crucial. It wouldn’t be an exaggeration to say that they account for 90% of performance. So this time, specifically targeting these two major knowledge areas, we’ll conduct a detailed analysis for you, peeling back the layers.

This Series uses plain and understandable language and selects a large number of examples to elaborate on the subtleties for you.

🧑‍💻 Target audience:

  • DBA
  • Database developers
  • Students

We will use MySQL as the demonstration database.


1. Mastering MySQL Functional Indexes

Functional indexes in MySQL are based on fields combined with functions, operators, or expressions. Unlike regular indexes, they leverage generated columns to store computed results without persisting the intermediate data, enhancing query performance significantly.

2. Use Cases for Functional Indexes

One of the most common use cases for functional indexes is handling date-related queries. For example, when you need to query parts of a datetime field. Consider a table t1 with a datetime field log_time:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<localhost|mysql>show create table t1\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `log_time` datetime(6) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_log_time` (`log_time`)
) ENGINE=InnoDB AUTO_INCREMENT=524268 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)

<localhost|mysql>select count(*) from t1;
+----------+
| count(*) |
+----------+
|   393216 |
+----------+
1 row in set (0.07 sec)

A query filtering by the date part of log_time initially performs poorly:

1
2
3
4
5
6
7
8
-- SQL 1
<localhost|mysql>select * from t1 where date(log_time) = '2100-02-02';
+--------+----------------------------+
| id     | log_time                   |
+--------+----------------------------+
| 524267 | 2100-02-02 08:09:09.123972 |
+--------+----------------------------+
1 row in set (0.09 sec)

The execution plan reveals a full table scan despite using an index:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<localhost|mysql>explain select * from t1 where date(log_time) = '2100-02-02'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: index
possible_keys: NULL
          key: idx_log_time
      key_len: 9
          ref: NULL
         rows: 392413
     filtered: 100.00
        Extra: Using where; Using index
1 row in set, 1 warning (0.00 sec)

Creating a functional index on date(log_time) improves performance dramatically:

1
2
3
<localhost|mysql>alter table t1 add key idx_func_index_1((date(log_time)));
Query OK, 0 rows affected (2.76 sec)
Records: 0  Duplicates: 0  Warnings: 0

The query now executes instantly:

1
2
3
4
5
6
7
<localhost|mysql>select * from t1 where date(log_time) = '2100-02-02';
+--------+----------------------------+
| id     | log_time                   |
+--------+----------------------------+
| 524267 | 2100-02-02 08:09:09.123972 |
+--------+----------------------------+
1 row in set (0.00 sec)

The execution plan confirms optimal usage of the functional index:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<localhost|mysql>explain select * from t1 where date(log_time) = '2100-02-02'\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: ref
possible_keys: idx_func_index_1
          key: idx_func_index_1
      key_len: 4
          ref: const
         rows: 1
     filtered: 100.00
        Extra: NULL
1 row in set, 1 warning (0.00 sec)

3. Handling JSON Fields with Functional Indexes

When working with JSON fields, direct indexing using operators like ->> may fail. Consider the following example:

1
2
3
4
5
<localhost|mysql>create table t2 (id int primary key, r1 json);
Query OK, 0 rows affected (0.09 sec)

<localhost|mysql>alter table t2 add key idx_func_index_2((r1->>'$.x'));
ERROR 3757 (HY000): Cannot create a functional index on an expression that returns a BLOB or TEXT. Please consider using CAST.

To resolve this, explicitly cast the JSON value to the appropriate type:

1
2
3
<localhost|mysql>alter table t2 add key idx_func_index_2((cast(r1->>'$.x' as char(1)) collate utf8mb4_bin));
Query OK, 0 rows affected (0.07 sec)
Records: 0  Duplicates: 0  Warnings: 0

3. Can Functional Indexes Replace Prefix Indexes?

While functional indexes are powerful, they cannot universally replace prefix indexes. Functional indexes require exact matches to the defined function for query optimization. For example, consider a table t3 with various indexes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<localhost|mysql>show create table t3\G
*************************** 1. row ***************************
      Table: t3
Create Table: CREATE TABLE `t3` (
 `id` bigint unsigned NOT NULL AUTO_INCREMENT,
 `r1` char(36) DEFAULT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `id` (`id`),
 KEY `idx_r1_prefix` (`r1`(8)),
 KEY `idx_func_index_3` ((left(`r1`,8))),
 KEY `idx_func_index_4` ((substr(`r1`,1,8)))
) ENGINE=InnoDB AUTO_INCREMENT=249 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)

Different SQL statements may use different indexes:

1
2
3
4
5
6
7
8
-- SQL 3
select * from t3 where r1 like 'de45c7d9%';

-- SQL 4
select * from t3 where left(r1,8) ='de45c7d9';

-- SQL 5
select * from t3 where substring(r1,1,8) ='de45c7d9';

Each query uses a different index:

 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<localhost|mysql>explain select * from t3 where r1 like 'de45c7d9%'\G
*************************** 1. row ***************************
          id: 1
 select_type: SIMPLE
       table: t3
  partitions: NULL
        type: range
possible_keys: idx_r1_prefix
         key: idx_r1_prefix
     key_len: 33
         ref: NULL
        rows: 1
    filtered: 100.00
       Extra: Using where
1 row in set, 1 warning (0.00 sec)

<localhost|mysql>explain select * from t3 where left(r1,8) ='de45c7d9'\G
*************************** 1. row ***************************
          id: 1
 select_type: SIMPLE
       table: t3
  partitions: NULL
        type: ref
possible_keys: idx_func_index_3
         key: idx_func_index_3
     key_len: 35
         ref: const
        rows: 1
    filtered: 100.00
       Extra: Using where
1 row in set, 1 warning (0.00 sec)

<localhost|mysql>explain select * from t3 where substring(r1,1,8) ='de45c7d9'\G
*************************** 1. row ***************************
          id: 1
 select_type: SIMPLE
       table: t3
  partitions: NULL
        type: ref
possible_keys: idx_func_index_4
         key: idx_func_index_4
     key_len: 35
         ref: const
        rows: 1
    filtered: 100.00
       Extra: Using where
1 row in set, 1 warning (0.00 sec)

4. Implementing Functional Indexes in Older MySQL Versions

For MySQL versions prior to 8.0.13, you can simulate functional indexes using generated columns. In MySQL 5.7, create a virtual generated column and index it:

1
2
ALTER TABLE your_table ADD (generated_column datatype) GENERATED ALWAYS AS (function(column)) VIRTUAL,
ADD INDEX idx_generated_column (generated_column);

In older versions like MySQL 5.6 or 5.5, you need to manually manage a redundant column:

  1. Add a new column to store the computed value
  2. Update this column whenever the source data changes
  3. Create an index on this new column

This approach requires careful planning and maintenance but can significantly improve query performance in legacy systems.

👋 See you in the next lesson.

What is SQLFlash?

SQLFlash is your AI-powered SQL Optimization Partner.

Based on AI models, we accurately identify SQL performance bottlenecks and optimize query performance, freeing you from the cumbersome SQL tuning process so you can fully focus on developing and implementing business logic.

How to use SQLFlash in a database?

Ready to elevate your SQL performance?

Join us and experience the power of SQLFlash today!.