70

I have got one index on a table that I would like to temporarily disable, I can't find any documentation suggesting that it's possible, though.

Reason: I've got an index that might be causing problems in queries unrelated to to any of the ones it was designed to speed up. It's a new index, and the system as a whole seems slower since it was introduced. I just want to be able to reliably eliminate it as the culprit, and this seems like the easiest way, other solution suggestions, as well as better question suggestions, are also welcome.

quodlibetor
  • 8,185
  • 4
  • 35
  • 48

3 Answers3

98

You can poke the system catalogue to disable an index:

update pg_index set indisvalid = false where indexrelid = 'test_pkey'::regclass

This means that the index won't be used for queries but will still be updated. It's one of the flags used for concurrent index building. Note that I've only done a quick test to see if the index still seems to be updated, caveat emptor.

araqnid
  • 127,052
  • 24
  • 157
  • 134
  • 8
    @schmichael I think if you set `indisready` to false, that will disable updates: http://www.postgresql.org/docs/current/static/catalog-pg-index.html – araqnid Feb 28 '12 at 20:53
  • 1
    you should also consider the restrictions related to that: http://serverfault.com/questions/300123/how-to-edit-system-catalogs-in-postgresql-8-1/ – Andreas Covidiot Sep 29 '14 at 12:29
  • 19
    doesn't work on RDS? `ERROR: permission denied for relation pg_index` – brauliobo Apr 30 '19 at 13:24
  • @araqnid Documentation seems updated since then. It clearly states: "False means the index is possibly incomplete: it must still be modified by INSERT/UPDATE operations, but it cannot safely be used for queries." – Hardcoded Jul 13 '23 at 15:01
55
begin;
drop index foo_ndx;
explain analyze select * from foo;
rollback;

I don't think there is a way to disable just one, though you can do this in a transaction to make recovering from it dead simple. You can also disable indexscan to disable all indices.

Also, make sure you are doing explain analyze on your queries.

Seth Robertson
  • 30,608
  • 7
  • 64
  • 57
  • 3
    Thanks. Unfortunately my problem is that I don't know which queries are being slowed down: everything that I installed the index for is super-snappy, it's just the system as a whole that's broken. So I need to disable the index globally for a few minutes, not just within one transaction. – quodlibetor May 26 '11 at 23:17
  • 1
    Giving you an upvote because that is the correct way to do it for a single query. If it wasn't for the stupid generality of my problem you would have fixed it. – quodlibetor May 26 '11 at 23:45
  • The advise to use an `EXPLAIN ANALYZE` is spot on for beginning to figure out what's going on. Then you can say, "why is the presence of an `INDEX` causing the time for an `index_scan` to go from X to Y?" – Sean May 27 '11 at 23:04
  • If you don't know which queries are slow, then turn on logging of long running statements and see. – Scott Marlowe May 28 '11 at 11:21
  • +1 This is a valid answer (as the question title is a bit ambiguous) and is exactly the one I needed. Clever trick. (You can do this with primary keys as well, just `ALTER TABLE foo DROP CONSTRAINT foo_pkey;` instead of `DROP INDEX foo_ndex;`.) – cdhowie Oct 19 '12 at 22:08
  • 3
    I've read that [this will obtain an `ACCESS EXCLUSIVE` lock on `foo_ndx`](http://stackoverflow.com/a/4693068/296829), how does that affect other concurrent queries? Will they use, or not use the index, or block until this transaction is done? – Steve Kehlet Mar 27 '14 at 17:52
2

I've created (and vetted) a function that does this for a wide variety of circumstances. You definitely need to be careful with it, but it does work.

CREATE OR REPLACE PROCEDURE global.enable_indexes(
    IN schema_name TEXT
,   IN table_name TEXT
,   IN index_name TEXT
,   IN enable_use BOOLEAN
,   IN enable_updates BOOLEAN
,   IN skip_essentials BOOLEAN DEFAULT TRUE
) AS
$$
DECLARE
    each_record RECORD;
BEGIN

/*
USAGE:

schema_name:
- filters down by schema name
- if NULL then does not filter
table_name:
- filters down by table name (careful - not schema qualified)
- if NULL then does not filter
index_name:
- filters down by index name (careful - not schema qualified)
- if NULL then does not filter
enable_use:
- This SETs the index as being available for use.
- If enable_updates is FALSE, then automatically FALSE
enable_updates:
- This SETs the index to be updated (which doesn't imply that it is enabled for use)
- When this was previously FALSE for the given index, then when setting to true will also trigger a rebuild of the index
skip_essentials:
- When this is true, PRIMARY and UNIQUE indexes will not be included in the scope (no changes).
- Optional
*/


IF array_replace(ARRAY[schema_name, table_name, index_name], NULL, '') <@ ARRAY[''] THEN
    RAISE EXCEPTION 'Error: Must specify at least one of schema_name | table_name | index_name';
END IF;

IF enable_updates IS FALSE THEN
    enable_use := FALSE;
    RAISE INFO 'FYI: Because enable_updates is FALSE, then likewise enable_use must be as well';
END IF;

FOR each_record IN
    select
        indexrelid
    ,   (schemaname||'.'||indexname) as index_name
    ,   indisvalid
    ,   indisready
    ,   (schemaname||'.'||tablename) as table_name
    ,   (CASE WHEN indisready IS FALSE and enable_use IS TRUE AND enable_updates IS TRUE THEN TRUE ELSE FALSE END) as needs_rebuilding
    FROM pg_indexes, pg_index
    WHERE
        indexrelid = (schemaname||'.'||indexname)::regclass
    AND case when schema_name <> '' THEN schemaname = schema_name ELSE TRUE END
    AND case when table_name <> '' THEN tablename = table_name ELSE TRUE END
    AND case when index_name <> '' THEN indexname = index_name ELSE TRUE END
    AND case when true THEN least(indisprimary, indisunique) = FALSE ELSE TRUE END
    AND case when skip_essentials THEN least(indisprimary, indisunique) = FALSE ELSE TRUE END
LOOP
    BEGIN

    RAISE INFO 'Set index % to have use % and updates %.'
        ,   each_record.index_name
        ,   (case when each_record.indisvalid AND enable_use THEN 'enabled (not changed)' WHEN NOT each_record.indisvalid AND enable_use THEN 'enabled (changed)' else 'disabled' END)
        ,   (case when each_record.indisready AND enable_updates THEN 'enabled (not changed)' WHEN NOT each_record.indisready AND enable_updates THEN 'enabled (changed)' else 'disabled' END)
    ;

    UPDATE pg_index
    SET
        indisvalid = enable_use
    ,   indisready = enable_updates
    WHERE
        indexrelid = each_record.indexrelid
    ;

    IF each_record.needs_rebuilding THEN
        RAISE INFO '... Reindexing and Analyzing %', each_record.index_name;
        EXECUTE format('REINDEX INDEX %1$s; ANALYZE %2$s;', each_record.index_name, each_record.table_name);
    END IF;

    COMMIT;

    END;

END LOOP;

END
$$
LANGUAGE plpgsql
;
Alexi Theodore
  • 1,177
  • 10
  • 16
  • Damn procedure! I have `\d tags` not showing indexes on `tags` now (this is preceded in time by running `call enable_indexes('', 'tags', '', true, true, false);` and then `call enable_indexes('', 'tags', '', false, false, false);`. I also had `f` as the value of `pg_catalog.pg_class.relhasindex` where `relname = 'tags'`. I have updated`relhasindex = 't'` but indexes on `tags` still are not shown. Now I am running `reindex table tags`. – porton Aug 07 '21 at 14:07
  • `reindex table tags;` apparently solved the problem. – porton Aug 07 '21 at 14:08
  • The procedure must execute `ANALYZE` not per an index but once per a table after the main loop is done. – Sergey Nevmerzhitsky Apr 11 '23 at 16:27