DEV Community

Cover image for The Index You Can Turn Off: MySQL 8 Invisible Indexes
Igor Nosatov
Igor Nosatov

Posted on

The Index You Can Turn Off: MySQL 8 Invisible Indexes

What Is an Invisible Index?

Think of a light switch. The bulb isn't burned out. The wires are still there. You just flipped it off. Flip it back on anytime โ€” instant light.

An invisible index is an index that:

  • Physically exists (takes up space, gets updated on every INSERT/UPDATE/DELETE)
  • Is ignored by the query optimizer when building execution plans
  • Can be toggled instantly with a single SQL command
-- Hide the index from the optimizer
ALTER TABLE orders ALTER INDEX idx_status INVISIBLE;

-- Bring it back
ALTER TABLE orders ALTER INDEX idx_status VISIBLE;
Enter fullscreen mode Exit fullscreen mode

No rebuild. No downtime. No drama. Just instant.


Three Scenarios Where This Changes Everything

๐Ÿ” Scenario 1: Safe Index Removal (My Exact Problem)

Instead of dropping first and praying, you hide first and observe:

-- Step 1: hide the index
ALTER TABLE orders ALTER INDEX idx_old_status INVISIBLE;

-- Step 2: monitor for a few days โ€” slow query log, dashboards, alerts
-- Step 3a: everything's fine โ†’ drop it for real
ALTER TABLE orders DROP INDEX idx_old_status;

-- Step 3b: something breaks โ†’ restore in under a second
ALTER TABLE orders ALTER INDEX idx_old_status VISIBLE;
Enter fullscreen mode Exit fullscreen mode

The difference between "delete and hope" and "disable and observe" is the difference between confidence and anxiety.


๐Ÿงช Scenario 2: A/B Testing Index Strategies

You have a slow query. You have two index candidates. Which one wins?

-- Create both, keep one invisible
CREATE INDEX idx_v1 ON orders (status, created_at);
CREATE INDEX idx_v2 ON orders (created_at, status) INVISIBLE;

-- Test with idx_v1 active
EXPLAIN SELECT * FROM orders WHERE status = 'pending' ORDER BY created_at;

-- Swap them โ€” zero rebuild time
ALTER TABLE orders ALTER INDEX idx_v1 INVISIBLE;
ALTER TABLE orders ALTER INDEX idx_v2 VISIBLE;

-- Test with idx_v2 active
EXPLAIN SELECT * FROM orders WHERE status = 'pending' ORDER BY created_at;
Enter fullscreen mode Exit fullscreen mode

Clean, controlled experiment on your live database โ€” no migration scripts, no staging guesswork.


๐Ÿšจ Scenario 3: Emergency Index Kill Switch

Occasionally the optimizer picks a "bad" index and tanks performance. Before invisible indexes, your options were grim: wait it out, use query hints everywhere, or drop the index entirely.

-- The optimizer chose poorly โ€” kill it without dropping
ALTER TABLE orders ALTER INDEX idx_causing_problems INVISIBLE;
-- Queries immediately re-plan without this index
Enter fullscreen mode Exit fullscreen mode

Now you have breathing room. Investigate at your own pace, then decide: drop it permanently, or turn it back on once you understand the root cause.


Under the Hood: What's Actually Happening

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                  MySQL 8 Query Flow                  โ”‚
โ”‚                                                       โ”‚
โ”‚  SQL Query                                            โ”‚
โ”‚      โ”‚                                                โ”‚
โ”‚      โ–ผ                                                โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                      โ”‚
โ”‚  โ”‚  Optimizer  โ”‚ โ”€โ”€โ–บ sees VISIBLE indexes only        โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                      โ”‚
โ”‚         โ”‚                                             โ”‚
โ”‚         โ–ผ                                             โ”‚
โ”‚    Execution Plan                                     โ”‚
โ”‚                                                       โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚            Storage Engine (InnoDB)              โ”‚  โ”‚
โ”‚  โ”‚                                                 โ”‚  โ”‚
โ”‚  โ”‚  idx_status   [VISIBLE]   โ—„โ”€โ”€ used in queries   โ”‚  โ”‚
โ”‚  โ”‚  idx_old      [INVISIBLE] โ—„โ”€โ”€ maintained,       โ”‚  โ”‚
โ”‚  โ”‚                                but never chosen  โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
Enter fullscreen mode Exit fullscreen mode

The crucial detail: InnoDB keeps invisible indexes fully up to date. Every write still updates them. This is exactly why toggling back to VISIBLE is instant โ€” there's no rebuild needed because the data was never stale.

The cost: invisible indexes still consume disk space and add write overhead. They're a temporary tool, not a permanent hiding place.


Inspect Your Index Visibility

SELECT 
    TABLE_NAME,
    INDEX_NAME,
    IS_VISIBLE,
    INDEX_TYPE,
    COLUMN_NAME
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_SCHEMA = 'your_db'
ORDER BY TABLE_NAME, INDEX_NAME;
Enter fullscreen mode Exit fullscreen mode

Sample output:

+------------+----------------+------------+------------+-------------+
| TABLE_NAME | INDEX_NAME     | IS_VISIBLE | INDEX_TYPE | COLUMN_NAME |
+------------+----------------+------------+------------+-------------+
| orders     | PRIMARY        | YES        | BTREE      | id          |
| orders     | idx_status     | YES        | BTREE      | status      |
| orders     | idx_old_stuff  | NO         | BTREE      | legacy_col  |
+------------+----------------+------------+------------+-------------+
Enter fullscreen mode Exit fullscreen mode

IS_VISIBLE = NO โ€” that's your invisible index, sitting quietly in the shadows.


The Hidden Superpower: Force-Enable Invisible Indexes Per Session

Here's a trick most people don't know: you can make invisible indexes visible for a single session โ€” without affecting anyone else.

-- Enable invisible indexes for this session only
SET SESSION optimizer_switch = 'use_invisible_indexes=on';

-- Now EXPLAIN will factor in invisible indexes
EXPLAIN SELECT * FROM orders WHERE status = 'pending';

-- Reset when done
SET SESSION optimizer_switch = 'use_invisible_indexes=off';
Enter fullscreen mode Exit fullscreen mode

Use this to benchmark an invisible index before turning it on. You can see exactly what query plans look like with and without it โ€” production traffic untouched.


Create an Index as Invisible from the Start

Useful when you're pre-building infrastructure for a feature that isn't live yet:

-- Add the index silently โ€” no optimizer impact yet
CREATE INDEX idx_new_feature ON orders (user_id, status) INVISIBLE;

-- Deploy your application code
-- Verify everything looks right in staging/canary
-- Then flip the switch
ALTER TABLE orders ALTER INDEX idx_new_feature VISIBLE;
Enter fullscreen mode Exit fullscreen mode

It's like laying the tracks before the train arrives.


Find Your Deletion Candidates (Then Make Them Invisible First)

MySQL's sys schema can surface indexes that haven't been used since the last server restart:

SELECT 
    object_schema,
    object_name,
    index_name
FROM sys.schema_unused_indexes
WHERE object_schema NOT IN ('mysql', 'sys', 'performance_schema', 'information_schema');
Enter fullscreen mode Exit fullscreen mode

Got a list? Resist the urge to drop them immediately.

Make them invisible. Wait a week. Watch your slow query log. Check your APM dashboards. If nothing screams โ€” then drop.


The New Mental Model

Old workflow:

Suspect index is unused โ†’ Drop it โ†’ Hope for the best โ†’ 3 AM phone call
Enter fullscreen mode Exit fullscreen mode

New workflow:

Suspect index is unused
  โ†’ ALTER ... INVISIBLE
  โ†’ Monitor for 3โ€“7 days
  โ†’ Check slow_query_log, metrics, alerts
  โ†’ All clear? DROP INDEX
  โ†’ Something broke? VISIBLE in one second
Enter fullscreen mode Exit fullscreen mode

It's a small feature. But it shifts you from reactive to deliberate when managing schema changes โ€” and that shift is worth a lot at 3 AM.


Quick Reference

Action SQL
Hide index ALTER TABLE t ALTER INDEX idx INVISIBLE;
Show index ALTER TABLE t ALTER INDEX idx VISIBLE;
Create hidden CREATE INDEX idx ON t (col) INVISIBLE;
Test in session SET SESSION optimizer_switch = 'use_invisible_indexes=on';
Find invisible SELECT * FROM INFORMATION_SCHEMA.STATISTICS WHERE IS_VISIBLE = 'NO';
Find unused SELECT * FROM sys.schema_unused_indexes;

Available since MySQL 8.0. No plugin required. No configuration flag. Just SQL.

Top comments (0)