MySQL Composite Indexes

This article describes composite indexes in MySQL, that is, indexes built on multiple columns.

In MySQL, a composite index is also called a composite index or a multi-column index, which is an index on multiple columns. MySQL composite indexes allow you to use up to 16 columns.

MySQL Composite Index Syntax

To create a composite index, use the CREATE INDEX statement:

CREATE INDEX index_name
ON table_name(column_1, column_2, column_3);

Here, an index named index_name is created on the column_1, column_2, and column_3 3 columns.

MySQL Composite Index Rules

When defining a multi-column index, the commonly used columns in the WHERE clause placed at the beginning of the columns list, and the uncommonly used columns should be placed in the following. Otherwise, the MySQL optimizer may not use the index.

For example, the following statement defines an index on the a, b and c columns:

CREATE INDEX index_name
ON table_name(a, b, c);

In the above syntax, the MySQL optimizer will consider using indexes in the following cases:

WHERE a = v1 and b = v2 and c = v3;

or

WHERE a = v1 and b = v2;

or

WHERE a = v1;

However, indexes are not considered for use in the following cases:

WHERE  c = v3;

or

WHERE b = v2 and c = v3;

That is, if the condition columns do not form the leftmost prefix of the index, the query optimizer cannot use the index to perform lookups.

MySQL multi-column index example

Let ’s demonstrate a MySQL multi-column index in the customer table from the Sakila sample database .

Use the following statement to view the customer table information:

DESC customer;
+-------------+-------------------+------+-----+-------------------+-----------------------------------------------+
| Field       | Type              | Null | Key | Default           | Extra                                         |
+-------------+-------------------+------+-----+-------------------+-----------------------------------------------+
| customer_id | smallint unsigned | NO   | PRI | NULL              | auto_increment                                |
| store_id    | tinyint unsigned  | NO   | MUL | NULL              |                                               |
| first_name  | varchar(45)       | NO   |     | NULL              |                                               |
| last_name   | varchar(45)       | NO   | MUL | NULL              |                                               |
| email       | varchar(50)       | YES  |     | NULL              |                                               |
| address_id  | smallint unsigned | NO   | MUL | NULL              |                                               |
| active      | tinyint(1)        | NO   |     | 1                 |                                               |
| create_date | datetime          | NO   |     | NULL              |                                               |
| last_update | timestamp         | YES  |     | CURRENT_TIMESTAMP | DEFAULT_GENERATED on update CURRENT_TIMESTAMP |
+-------------+-------------------+------+-----+-------------------+-----------------------------------------------+

Use the SHOW INDEXES statement to view the indexes in the customer table:

SHOW INDEXES FROM customer;
+----------+------------+-------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table    | Non_unique | Key_name          | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+----------+------------+-------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| customer |          0 | PRIMARY           |            1 | customer_id | A         |         599 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| customer |          1 | idx_fk_store_id   |            1 | store_id    | A         |           2 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| customer |          1 | idx_fk_address_id |            1 | address_id  | A         |         599 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| customer |          1 | idx_last_name     |            1 | last_name   | A         |         598 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
+----------+------------+-------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
4 rows in set (0.01 sec)

Several indexes already exist in the customer table, in order to demonstrate the multi-column index, we delete the idx_last_name index first:

DROP INDEX idx_last_name ON customer;

Create an index named idx_last_name_first_name on last_name and first_name columns using the following statement:

CREATE INDEX idx_last_name_first_name
ON customer (last_name, first_name);

View the information of the customer table:

SHOW INDEXES FROM customer;
+----------+------------+--------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table    | Non_unique | Key_name                 | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+----------+------------+--------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| customer |          0 | PRIMARY                  |            1 | customer_id | A         |         599 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| customer |          1 | idx_fk_store_id          |            1 | store_id    | A         |           2 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| customer |          1 | idx_fk_address_id        |            1 | address_id  | A         |         599 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| customer |          1 | idx_last_name_first_name |            1 | last_name   | A         |         598 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| customer |          1 | idx_last_name_first_name |            2 | first_name  | A         |         599 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
+----------+------------+--------------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
5 rows in set (0.01 sec)

We found the customer_last_name_first_name_idx index is defined on last_name and first_name columns .

Use the EXPLAIN statement check whether the following statements use indexes:

EXPLAIN
SELECT * FROM customer
WHERE last_name = 'A'
AND first_name = 'B';
+----+-------------+----------+------------+------+--------------------------+--------------------------+---------+-------------+------+----------+-------+
| id | select_type | table    | partitions | type | possible_keys            | key                      | key_len | ref         | rows | filtered | Extra |
+----+-------------+----------+------------+------+--------------------------+--------------------------+---------+-------------+------+----------+-------+
|  1 | SIMPLE      | customer | NULL       | ref  | idx_last_name_first_name | idx_last_name_first_name | 364     | const,const |    1 |   100.00 | NULL  |
+----+-------------+----------+------------+------+--------------------------+--------------------------+---------+-------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

Here, when the WHERE condition has last_name and first_name, the MySQL optimizer chooses to use the index.

Use the EXPLAIN statement check whether the following statements use indexes:

EXPLAIN
SELECT * FROM customer
WHERE last_name = 'A';
+----+-------------+----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-------+
| id | select_type | table    | partitions | type | possible_keys            | key                      | key_len | ref   | rows | filtered | Extra |
+----+-------------+----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | customer | NULL       | ref  | idx_last_name_first_name | idx_last_name_first_name | 182     | const |    1 |   100.00 | NULL  |
+----+-------------+----------+------------+------+--------------------------+--------------------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)

Here, when there is only last_name in the WHERE condition, the MySQL optimizer chooses to use the index. This is because last_name the column is the first column in the index.

Use the EXPLAIN statement check whether the following statement use indexes:

EXPLAIN
SELECT * FROM customer
WHERE first_name = 'B';
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | customer | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  599 |    10.00 | Using where |
+----+-------------+----------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

Here, when there is only first_name in the WHERE condition, the MySQL optimizer does not chooses to use the index. This is because the first_name column is not the first column of the index.

Conclusion

This article discusses MySQL multicolumn indexes and how the order of multicolumn indexes affects the MySQL optimizer.

When you define a multi-column index, you should always consider the business context to determine which columns are frequently used for lookups, and place those columns at the beginning of the column list when defining the index.