Anda mendapatkan pengecualian menggunakan now()
karena fungsinya bukan IMMUTABLE
(jelas) dan, dengan mengutip manual
:
Saya melihat dua cara untuk menggunakan indeks parsial (jauh lebih efisien):
1. Indeks parsial dengan kondisi menggunakan konstan tanggal:
CREATE INDEX queries_recent_idx ON queries_query (user_sid, created)
WHERE created > '2013-01-07 00:00'::timestamp;
Dengan asumsi created
sebenarnya didefinisikan sebagai timestamp
. Tidak akan berfungsi untuk memberikan timestamp
konstan untuk timestamptz
kolom (timestamp with time zone
). Pemeran dari timestamp
ke timestamptz
(atau sebaliknya) bergantung pada setelan zona waktu saat ini dan tidak dapat diubah . Gunakan konstanta tipe data yang cocok. Memahami dasar-dasar stempel waktu dengan / tanpa zona waktu:
Lepaskan dan buat ulang indeks itu pada jam-jam dengan lalu lintas rendah, mungkin dengan pekerjaan cron setiap hari atau setiap minggu (atau apa pun yang cukup baik untuk Anda). Membuat indeks cukup cepat, terutama indeks parsial yang relatif kecil. Solusi ini juga tidak perlu menambahkan apa pun ke tabel.
Dengan asumsi tidak ada akses bersamaan ke tabel, rekreasi indeks otomatis dapat dilakukan dengan fungsi seperti ini:
CREATE OR REPLACE FUNCTION f_index_recreate()
RETURNS void
LANGUAGE plpgsql AS
$func$
BEGIN
DROP INDEX IF EXISTS queries_recent_idx;
EXECUTE format('
CREATE INDEX queries_recent_idx
ON queries_query (user_sid, created)
WHERE created > %L::timestamp'
, LOCALTIMESTAMP - interval '30 days'); -- timestamp constant
-- , now() - interval '30 days'); -- alternative for timestamptz
END
$func$;
Telepon:
SELECT f_index_recreate();
now()
(seperti yang Anda miliki) setara dengan CURRENT_TIMESTAMP
dan mengembalikan timestamptz
. Transmisikan ke timestamp
dengan now()::timestamp
atau gunakan LOCALTIMESTAMP
sebagai gantinya.
db<>fiddle di sini
Lama sqlfiddle
Jika Anda harus berurusan dengan akses bersamaan ke tabel, gunakan DROP INDEX CONCURRENTLY
dan CREATE INDEX CONCURRENTLY
. Tetapi Anda tidak dapat menggabungkan perintah ini menjadi suatu fungsi karena, per dokumentasi
:
Jadi, dengan dua transaksi terpisah :
CREATE INDEX CONCURRENTLY queries_recent_idx2 ON queries_query (user_sid, created)
WHERE created > '2013-01-07 00:00'::timestamp; -- your new condition
Kemudian:
DROP INDEX CONCURRENTLY IF EXISTS queries_recent_idx;
Secara opsional, ganti nama menjadi nama lama:
ALTER INDEX queries_recent_idx2 RENAME TO queries_recent_idx;
2. Indeks parsial dengan kondisi pada tag "diarsipkan"
Tambahkan archived
tag ke meja Anda:
ALTER queries_query ADD COLUMN archived boolean NOT NULL DEFAULT FALSE;
UPDATE
kolom pada interval yang Anda pilih untuk "menghentikan" baris lama dan membuat indeks seperti:
CREATE INDEX some_index_name ON queries_query (user_sid, created)
WHERE NOT archived;
Tambahkan kondisi yang cocok ke kueri Anda (meskipun tampaknya berlebihan) untuk mengizinkannya menggunakan indeks. Periksa dengan EXPLAIN ANALYZE
apakah perencana kueri mengetahui - ia harus dapat menggunakan indeks untuk kueri pada tanggal yang lebih baru. Tapi itu tidak akan memahami kondisi yang lebih kompleks yang tidak sama persis.
Anda tidak perlu menghapus dan membuat ulang indeks, tetapi UPDATE
di atas meja mungkin lebih mahal daripada rekreasi indeks dan meja menjadi sedikit lebih besar.
Saya akan memilih yang pertama pilihan (rekreasi indeks). Sebenarnya, saya menggunakan solusi ini di beberapa database. Yang kedua menimbulkan pembaruan yang lebih mahal.
Kedua solusi mempertahankan kegunaannya dari waktu ke waktu, kinerja perlahan-lahan menurun karena lebih banyak baris usang dimasukkan dalam indeks.