MySQL Functional Indexes

March 14, 2019
Sunset in Crete by Olivier DASINI

Since MySQL 5.7 one can put indexes on expressions, aka functional indexes, using generated columns. Basically you first need to use the generated column to define the functional expression, then indexed this column.

Quite useful when dealing with JSON functions, you can find an example here and the documentation there.

Starting with MySQL 8.0.13 we have now an easiest way to create functional indexes (or functional key parts as mentioned in the documentation) \o/

Let’s see how with a quick practical example.

Below salaries table structure:

It contains some data

Let’s focus on the following query:
SELECT * FROM salaries WHERE YEAR(to_date)=1985

We have a full table scan ( type: ALL), meaning no index is used. Perhaps because there is no index on column to_date… 😉
So let’s add an index on to_date !

And run again the query with the hope of a better execution plan

Ouch! Still have a full table scan !
The index can’t be used because of the use of a function (YEAR()) on the indexed column (to_date).
BTW if you’re really surprise, maybe you should read this. 😉

This is the case when you need a functional index!

The syntax is very similar of the creation of a “regular” index. Although you must be aware of the double parentheses: (( <expression> ))
We can now see our new index named idx_year_to_date and the indexed expression year(to_date) :

Let’s test our query again

Here we go!
Now the query is able to use the index. And in this case we have a positive impact on the execution time.

It is also interesting to note that it is possible to use idx_to_date, the first index created (the non functional one) if we can rewrite the original query:

This saves an index, I mean less indexes to maintain for the engine. Also speaking of maintenance cost, the cost to maintain a functional index is higher than the cost of a regular one.

In the other side the execution plan is less good (query cost higher) and obviously you must rewrite the query.

Requirements and restrictions.

A primary key cannot be a functional index:

You can not index non-deterministic functions (RAND(), UNIX_TIMESTAMP(), NOW()…)

SPATIAL and FULLTEXT indexes cannot have functional key parts.

Conclusion

Functional index is an interesting and a relevant feature, it could be very useful to optimize your queries without rewrite them and especially when dealing with JSON documents and other complex types.

Obviously all the details you must know are in the MySQL documentation: Functional Key Parts
If you interested in the high level architecture and the low level design please read the workload.



Thanks for using MySQL!

Follow me on twitter

6 Responses to “MySQL Functional Indexes”

  1. […] MySQL Functional Indexes […]

  2. It would be nice if MariaDB will implements similar solution

  3. Well MariaDB is a forked of MySQL 5.1.
    First available source code of 5.1 was in 2005…
    Remember what was the #1 movie in the box office in 2005? : http://dasini.net/blog/2017/07/19/mysql-first-public-releases/

    IMHO that does not make much sense to hope that these 2 products will behave in the same way nor have the same features.

    Anyway MySQL 8 is Great! 🙂
    Thanks for your comment.

    Olivier

  4. Hi,
    thanks for your post.
    I would like to understand why Function-based index is not used in my case.

    Thanks and best regards.
    Michel Ramirez

    select count(*) from TSK_PLANNING_PLN => 4957

    select count(*) from TSK_PLANNING_PLN where PLN_END_DATE IS NULL => 1660

    Index => ALTER TABLE TSK_PLANNING_PLN ADD INDEX IND_MRO_PLN_END_DATE_COALESCE((COALESCE(PLN_END_DATE,STR_TO_DATE(‘9999/01/01′,’%Y/%m/%d’))));

    Request :

    EXPLAIN SELECT *
    FROM
    TSK_PLANNING_PLN pln USE INDEX (IND_MRO_PLN_END_DATE_COALESCE)
    WHERE
    COALESCE(PLN_END_DATE,STR_TO_DATE(‘9999/01/01′,’%Y/%m/%d’)) >= STR_TO_DATE(‘2020/12/21′,’%Y/%m/%d’)
    AND pln.pln_start_date 1505 lines.

  5. Sorry i forgot to show you, explain plan

    id|select_type|table|partitions|type|possible_keys |key|key_len|ref|rows|filtered|Extra |
    –|———–|—–|———-|—-|—————————–|—|——-|—|—-|——–|———–|
    1|SIMPLE |pln | |ALL |IND_MRO_PLN_END_DATE_COALESCE| | | |5032| 11.26|Using where|

    Best regard.

  6. Hello,
    well there are many reason why the optimizer would not use an index.
    One of them is because the optimizer think it is more efficient to not use it (usually because of the column cardinality).
    If you try your query with FORCE INDEX instead of USE INDEX what is the EXPLAIN result ?
    If you think the statistics are not accurate you can also run ANALYZE TABLE

    Cheers,
    Olivier