
Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: July 22, 2024
A database index is a data structure that can be defined on one or more columns of a table. An index is designed to enable fast data lookup. As a result, query performance can improve. Further, the query optimizer determines whether to use an index for a specific query. We can supply the query with hints to ignore one or more indexes. We do this when we find a query using an index when it shouldn’t.
Some relational databases, notably MySQL and SQL Server, provide SQL syntax to ignore index(es) in a query. However, it may not always be possible for a query to ignore an index. Therefore, a query will use an index if it’s required for the query plan even if an index hint is added to ignore the index.
In this tutorial, we’ll learn how to design a query to ignore an index. We’ll use MySQL and SQL Server database examples to illustrate the feature. PostgreSQL does not support ignoring an index for a specific query with index/optimizer hints. PostgreSQL does support dropping an index, and disabling index scans altogether, but this is not the focus of this article.
Let’s set up the sample schemas provided on GitHub. We’ll be using the table Program in the sample database called University. If we need to find what indexes already exist in the table, for MySQL we can use:
SHOW INDEXES
FROM Program;
For MySQL, we’ll use the predefined index program_department_id_fkey in the table Program.
For SQL Server we’ll use a new index. Therefore, let’s create a nonclustered column store index called csindx_simple in the table Program:
CREATE NONCLUSTERED COLUMNSTORE INDEX csindx_simple
ON Program (department_id, type);
We’ll use this index to demonstrate ignoring an index.
To begin with, let’s identify an index to ignore. Therefore, let’s use the query profiling clauses EXPLAIN ANALYZE with a query on the Program table. Thereupon, we find that the query is using the program_department_id_fkey index to make an index lookup on the Program table:
EXPLAIN
ANALYZE
SELECT name, type, department_id
FROM Program
WHERE department_id=1
GROUP BY name, type, department_id
ORDER BY department_id;
+---------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+---------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Table scan on (cost=2.53..4.8 rows=8) (actual time=0.145..0.147 rows=8 loops=1)
-> Temporary table with deduplication (cost=2.2..2.2 rows=8) (actual time=0.141..0.141 rows=8 loops=1)
-> Index lookup on Program using program_department_id_fkey (department_id=1) (cost=1.4 rows=8) (actual time=0.0899..0.0997 rows=8 loops=1)
|
+---------------------------------------------------------------------------------------------------------------------------------------------------+
Thereupon, let’s use the program_department_id_fkey index to demonstrate ignoring an index.
The simplest way we can ignore one or more indexes is to use the USE INDEX() index hint in the query. Furthermore, we should specify only the indexes that we want to include. Accordingly, the query planner automatically ignores any index that’s not specified. Let’s analyze the query plan for the same query using the USE INDEX() index hint.
Furthermore, if we want to ignore all indexes we should specify an empty list:
EXPLAIN
ANALYZE
SELECT name, type, department_id
FROM Program
USE INDEX()
WHERE department_id=1
GROUP BY name, type, department_id
ORDER BY department_id;
+----------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+----------------------------------------------------------------------------------------------------------------+
| -> Table scan on (cost=3.66..5.68 rows=4.8) (actual time=0.113..0.115 rows=8 loops=1)
-> Temporary table with deduplication (cost=3.13..3.13 rows=4.8) (actual time=0.11..0.11 rows=8 loops=1)
-> Filter: (Program.department_id = 1) (cost=2.65 rows=4.8) (actual time=0.0546..0.0729 rows=8 loops=1)
-> Table scan on Program (cost=2.65 rows=24) (actual time=0.0526..0.0671 rows=24 loops=1)
|
+----------------------------------------------------------------------------------------------------------------+
Accordingly, the query doesn’t use the program_department_id_fkey index this time and instead makes a table scan.
Another method we can use with a query to ignore one or more indexes is to use the IGNORE INDEX() index hint. Furthermore, this type of index hint can be used to explicitly specify an index list to ignore.
Let’s run the same query and specify program_department_id_fkey as the index to ignore:
EXPLAIN ANALYZE
SELECT name, type, department_id
FROM Program IGNORE INDEX(program_department_id_fkey)
WHERE department_id=1
GROUP BY name, type, department_id
ORDER BY department_id;
+---------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+---------------------------------------------------------------------------------------------------------------+
| -> Table scan on (cost=3.66..5.68 rows=4.8) (actual time=0.132..0.134 rows=8 loops=1)
-> Temporary table with deduplication (cost=3.13..3.13 rows=4.8) (actual time=0.129..0.129 rows=8 loops=1)
-> Filter: (Program.department_id = 1) (cost=2.65 rows=4.8) (actual time=0.0665..0.0861 rows=8 loops=1)
-> Table scan on Program (cost=2.65 rows=24) (actual time=0.0641..0.0799 rows=24 loops=1)
|
+---------------------------------------------------------------------------------------------------------------+
Indeed, the query doesn’t use the index and makes a table scan instead.
Furthermore, we can set the scope of the IGNORE INDEX() index hint to one of three options:
Additionally, the IGNORE INDEX without any scope-limiting sub-clause is equivalent to all three. To ignore an index in a specific scope only, let’s run the same query with the scope limited to GROUP BY:
EXPLAIN ANALYZE
SELECT name, type, department_id
FROM Program
IGNORE INDEX FOR GROUP BY(program_department_id_fkey)
WHERE department_id=1
GROUP BY name, type, department_id
ORDER BY department_id;
+-------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Table scan on (cost=4.33..6.6 rows=8) (actual time=0.636..0.639 rows=8 loops=1)
-> Temporary table with deduplication (cost=4..4 rows=8) (actual time=0.632..0.632 rows=8 loops=1)
-> Index lookup on Program using program_department_id_fkey (department_id=1) (cost=3.2 rows=8) (actual time=0.518..0.529 rows=8 loops=1)
|
+-------------------------------------------------------------------------------------------------------------------------------------------------+
As a result, the query is still using the index. This is because the index is not used for GROUP BY in the first place; therefore, it is not ignored. Let’s try limiting the scope to ORDER BY:
EXPLAIN ANALYZE
SELECT name, type, department_id
FROM Program
IGNORE INDEX FOR ORDER BY(program_department_id_fkey)
WHERE department_id=1
GROUP BY name, type, department_id
ORDER BY department_id;
+---------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+---------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Table scan on (cost=2.53..4.8 rows=8) (actual time=0.0891..0.0912 rows=8 loops=1)
-> Temporary table with deduplication (cost=2.2..2.2 rows=8) (actual time=0.0864..0.0864 rows=8 loops=1)
-> Index lookup on Program using program_department_id_fkey (department_id=1) (cost=1.4 rows=8) (actual time=0.0513..0.0572 rows=8 loops=1)
|
+---------------------------------------------------------------------------------------------------------------------------------------------------+
However, the query still uses the index. This is because the index is not used for ORDER BY in the first place; therefore, it is not ignored.
How about if we limit the scope to JOIN only:
EXPLAIN ANALYZE
SELECT name, type, department_id
FROM Program
IGNORE INDEX FOR JOIN(program_department_id_fkey)
WHERE department_id=1
GROUP BY name, type, department_id
ORDER BY department_id;
+---------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+---------------------------------------------------------------------------------------------------------------+
| -> Table scan on (cost=3.66..5.68 rows=4.8) (actual time=0.094..0.0956 rows=8 loops=1)
-> Temporary table with deduplication (cost=3.13..3.13 rows=4.8) (actual time=0.0926..0.0926 rows=8 loops=1)
-> Filter: (Program.department_id = 1) (cost=2.65 rows=4.8) (actual time=0.0399..0.0519 rows=8 loops=1)
-> Table scan on Program (cost=2.65 rows=24) (actual time=0.0384..0.0478 rows=24 loops=1)
|
+---------------------------------------------------------------------------------------------------------------+
Indeed, this time, the query ignores the index and uses a table scan. This is because the index is used for JOIN in the first place; therefore, it is ignored.
There’s yet another way we can ignore index/es in a query. That’s by supplying optimizer hints. Further, the optimizer hints to ignore indexes are introduced in MySQL 8.0 and have an equivalent for each of the index hints:
Index Hint | Optimizer Hint |
---|---|
IGNORE INDEX | NO_INDEX |
IGNORE INDEX FOR GROUP BY | NO_GROUP_INDEX |
IGNORE INDEX FOR ORDER BY | NO_ORDER_INDEX |
IGNORE INDEX FOR JOIN | NO_JOIN_INDEX |
Furthermore, a regular query can specify an optimizer hint within /*+ */. When we profile the example query by including a NO_INDEX optimizer hint we find that the query doesn’t use the index and instead makes a table scan:
EXPLAIN ANALYZE
SELECT /*+ NO_INDEX(Program program_department_id_fkey)*/ name, type, department_id
FROM Program
WHERE department_id =1
GROUP BY name, type, department_id
ORDER BY department_id;
+-------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-------------------------------------------------------------------------------------------------------------+
| -> Table scan on (cost=3.66..5.68 rows=4.8) (actual time=0.125..0.127 rows=8 loops=1)
-> Temporary table with deduplication (cost=3.13..3.13 rows=4.8) (actual time=0.123..0.123 rows=8 loops=1)
-> Filter: (Program.department_id = 1) (cost=2.65 rows=4.8) (actual time=0.0604..0.081 rows=8 loops=1)
-> Table scan on Program (cost=2.65 rows=24) (actual time=0.0583..0.0743 rows=24 loops=1)
|
+-------------------------------------------------------------------------------------------------------------+
When we use the scope limiting NO_INDEX_FOR_ORDER_BY, or the NO_INDEX_FOR_GROUP_BY, the query doesn’t ignore the index.
SQL Server supports two methods to ignore an index: the WITH(INDEX()) index hint and the query hint IGNORE_NONCLUSTERED_COLUMNSTORE_INDEX. To start, let’s set the SHOWPLAN_ALL to ON to list the query plan so that we can verify if a query ignores an index.
We can use the WITH(INDEX()) hint to explicitly specify an index list to use. Accordingly, the query planner automatically ignores any indexes that aren’t in the index list.
Let’s start with an example of using WITH to specify two indexes PK__Program__3213E83F9B13E52E, and csindx_simple:
SELECT name, type, department_id
FROM Program
WITH (INDEX(PK__Program__3213E83F9B13E52E,csindx_simple))
WHERE department_id =1
GROUP BY name, type, department_id
ORDER BY department_id;
|--Hash Match(Aggregate, HASH:([ 1 Hash Match Aggregate
|--Hash Match(Inner Join, HASH:([ 2 Hash Match Inner Join
|--Clustered Index Scan(OBJECT:([University].[dbo].[Program].[PK__Program__3213E83F9B13E52E]), 4 Clustered Index Scan Clustered Index Scan
|--Index Scan(OBJECT:([University].[dbo].[Program].[csindx_simple]), 4 Index Scan Index Scan
Indeed, the query uses the two indexes.
As an example, let’s ignore the csindx_simple index by specifying only the PK__Program__3213E83F9B13E52E index in the WITH(INDEX()) hint:
SELECT name, type, department_id
FROM Program
WITH (INDEX(PK__Program__3213E83F9B13E52E))
WHERE department_id =1
GROUP BY name, type, department_id
ORDER BY department_id;
|--Sort(DISTINCT ORDER BY:([University].[dbo].[Program].[name] ASC, [University].[dbo].[Program].[type] ASC)) 1 Sort Distinct Sort
|--Clustered Index Scan(OBJECT:([University].[dbo].[Program].[PK__Program__3213E83F9B13E52E]), 2 Clustered Index Scan Clustered Index Scan
As a result, the query ignores the csindx_simple index.
A nonclustered memory-optimized column store index can improve query performance. Further, SQL Server 2012 introduced a query hint called IGNORE_NONCLUSTERED_COLUMNSTORE_INDEX that doesn’t let a query use the nonclustered memory-optimized column store index.
To show its use, let’s include the query hint IGNORE_NONCLUSTERED_COLUMNSTORE_INDEX with the example query to not use a nonclustered column store index:
SELECT name, type, department_id
FROM Program
WHERE department_id =1 AND type='Major'
GROUP BY name, type, department_id
ORDER BY department_id
OPTION (IGNORE_NONCLUSTERED_COLUMNSTORE_INDEX);
|--Sort(DISTINCT ORDER BY:([University].[dbo].[Program].[name] ASC)) 1 Sort Distinct Sort
|--Clustered Index Scan(OBJECT:([University].[dbo].[Program].[PK__Program__3213E83F9B13E52E]), 2 Clustered Index Scan Clustered Index Scan
Accordingly, the query only uses the Clustered index scan along with a distinct order sort. Conversely, when we don’t include a query hint to ignore the nonclustered column store index, the query uses a Hash Match Aggregate operator to build a hash table in the memory.
In this article, we learned about running an SQL query by excluding all or specific indexes. However, only some relational databases support ignoring indexes in a query. We used an example query based on sample schemas in MySQL and SQL Server databases to exclude, or ignore, specific indexes. Further, the examples showed the different methods supported by these databases to ignore an index in a query.
In conclusion, we can ignore non-essential indexes in a query by using database-specific methods and SQL clauses. Furthermore, most of these methods use index or optimizer hints in a query.