“Index type” can mean three different things in MySQL: the data structure, the role of the index and the kind of search it supports. Here they are, one at a time.
By data structure
| Structure | Used by | Good for |
|---|---|---|
B-tree (BTREE) | InnoDB and MyISAM, the default | Equality, ranges, sorting, prefix searches |
| Hash | MEMORY engine (and InnoDB’s internal adaptive hash index) | Equality lookups only |
| R-tree | SPATIAL indexes | Geographic and geometric data |
| Inverted index | FULLTEXT indexes | Searching words in text |
For nearly every index you create in an InnoDB table, the structure is a B-tree. You can write USING BTREE, but it is the default.
Hash indexes
A hash index finds exact matches very fast, but cannot do ranges, sorting or prefixes, because hashing destroys order. MySQL supports explicit hash indexes only in MEMORY tables. InnoDB builds its own adaptive hash index in memory, automatically, for hot lookups. You do not control it per table, and can switch it on or off with innodb_adaptive_hash_index.
CREATE TABLE session_cache (token CHAR(16) NOT NULL, user_id INT NOT NULL, PRIMARY KEY (token) USING HASH) ENGINE = MEMORY;
SHOW CREATE TABLE session_cache\G
*************************** 1. row ***************************
Table: session_cache
Create Table: CREATE TABLE `session_cache` (
`token` char(16) NOT NULL,
`user_id` int NOT NULL,
PRIMARY KEY (`token`) USING HASH
) ENGINE=MEMORY DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set
MEMORY tables lose their data when the server restarts, so they are for caches and temporary work, not for real data (Storage Engines).
By role (InnoDB)
| Role | What it is |
|---|---|
| Primary key / clustered index | The table itself, stored in primary key order. There is exactly one per table. A lookup by primary key reads the row directly |
| Secondary index | Any other index. It stores the indexed columns plus the primary key. A lookup finds the primary key first, then fetches the row from the clustered index |
| Unique index | A secondary index that also forbids duplicates (UNIQUE) |
| Composite index | An index over several columns (Composite Indexes) |
| Covering index | Any index that contains all the columns a query needs (Covering Indexes) |
| Prefix index | An index on the first N characters of a text column |
| Functional index | An index on an expression, e.g. (LOWER(email)) (8.0.13+) |
| Descending index | An index stored in descending order (8.0+) |
| Invisible index | An index that exists but is hidden from the optimiser (8.0+) |
| Multi-valued index | An index on values of a JSON array (8.0.17+) |
Because every secondary index contains the primary key, a large primary key makes every index larger. That is one reason to use a short INT or BIGINT as primary key.
By kind of search
| Index | Created with | Answers |
|---|---|---|
| Normal / unique | CREATE INDEX, UNIQUE | =, <, >, BETWEEN, IN, ORDER BY, LIKE 'abc%', MIN, MAX |
| FULLTEXT | CREATE FULLTEXT INDEX | MATCH(...) AGAINST(...): words and phrases in text (Full-Text Search) |
| SPATIAL | CREATE SPATIAL INDEX | Geometry searches: points in an area, nearest places (ST_Contains, ST_Distance_Sphere) |
CREATE TABLE places (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(30) NOT NULL,
location POINT NOT NULL SRID 4326,
SPATIAL INDEX sp_location (location)
);
INSERT INTO places (name, location) VALUES ('Pune', ST_GeomFromText('POINT(18.52 73.86)', 4326)), ('Mumbai', ST_GeomFromText('POINT(19.07 72.88)', 4326));
SELECT name, ROUND(ST_Distance_Sphere(location, ST_GeomFromText('POINT(18.52 73.86)', 4326)) / 1000) AS km_from_pune FROM places ORDER BY km_from_pune;
+--------+--------------+
| name | km_from_pune |
+--------+--------------+
| Pune | 0 |
| Mumbai | 120 |
+--------+--------------+
2 rows in set
Spatial work is a field of its own. This is only a glimpse: the distance between Pune and Mumbai in kilometres, with an R-tree index declared on the point column.
Which one should I use?
- Normal B-tree for 95 percent of cases.
- FULLTEXT when you search for words in text columns.
- SPATIAL for map and location queries.
- Hash only in MEMORY tables, which you rarely need.
Not an index type, but related: partitioning
Splitting a table into partitions is a different tool. It helps with maintenance (dropping old data fast) and sometimes with scanning by range (Partitioning).