Urutan memiliki celah untuk mengizinkan penyisipan bersamaan. Mencoba menghindari celah atau menggunakan kembali ID yang dihapus menciptakan masalah kinerja yang mengerikan. Lihat FAQ wiki PostgreSQL.
PostgreSQL SEQUENCE
s digunakan untuk mengalokasikan ID. Ini hanya akan meningkat, dan mereka dibebaskan dari aturan rollback transaksi biasa untuk mengizinkan beberapa transaksi mengambil ID baru secara bersamaan. Artinya, jika transaksi dibatalkan, ID tersebut "dibuang"; tidak ada daftar ID "gratis" yang disimpan, hanya penghitung ID saat ini. Urutan juga biasanya bertambah jika database dimatikan dengan tidak benar.
Kunci sintetis (ID) tidak berarti omong-omong. Urutan mereka tidak signifikan, satu-satunya properti signifikansi mereka adalah keunikan. Anda tidak dapat secara bermakna mengukur seberapa "berjauhan" dua ID, Anda juga tidak dapat secara bermakna mengatakan jika satu lebih besar atau lebih kecil dari yang lain. Yang bisa Anda lakukan hanyalah mengatakan "sama" atau "tidak sama". Ada lagi yang tidak aman. Anda seharusnya tidak peduli dengan kesenjangan.
Jika Anda memerlukan urutan tanpa celah yang menggunakan kembali ID yang dihapus, Anda dapat memilikinya, Anda hanya perlu menyerahkan sejumlah besar kinerja untuk itu - khususnya, Anda tidak dapat memiliki konkurensi apa pun pada INSERT
s sama sekali, karena Anda harus memindai tabel untuk ID gratis terendah, mengunci tabel untuk menulis sehingga tidak ada transaksi lain yang dapat mengklaim ID yang sama. Coba telusuri "urutan tanpa celah postgresql".
Pendekatan paling sederhana adalah dengan menggunakan tabel penghitung dan fungsi yang mendapatkan ID berikutnya. Berikut adalah versi umum yang menggunakan tabel penghitung untuk menghasilkan ID tanpa celah berturut-turut; itu tidak menggunakan kembali ID.
CREATE TABLE thetable_id_counter ( last_id integer not null );
INSERT INTO thetable_id_counter VALUES (0);
CREATE OR REPLACE FUNCTION get_next_id(countertable regclass, countercolumn text) RETURNS integer AS $$
DECLARE
next_value integer;
BEGIN
EXECUTE format('UPDATE %s SET %I = %I + 1 RETURNING %I', countertable, countercolumn, countercolumn, countercolumn) INTO next_value;
RETURN next_value;
END;
$$ LANGUAGE plpgsql;
COMMENT ON get_next_id(countername regclass) IS 'Increment and return value from integer column $2 in table $1';
Penggunaan:
INSERT INTO dummy(id, blah)
VALUES ( get_next_id('thetable_id_counter','last_id'), 42 );
Perhatikan bahwa ketika satu transaksi terbuka telah memperoleh ID, semua transaksi lain yang mencoba memanggil get_next_id
akan memblokir sampai transaksi pertama melakukan atau memutar kembali. Ini tidak dapat dihindari dan untuk ID tanpa celah dan dirancang.
Jika Anda ingin menyimpan beberapa penghitung untuk tujuan yang berbeda dalam sebuah tabel, cukup tambahkan parameter ke fungsi di atas, tambahkan kolom ke tabel penghitung, dan tambahkan WHERE
klausa ke UPDATE
yang cocok dengan parameter ke kolom yang ditambahkan. Dengan begitu Anda dapat memiliki beberapa baris penghitung yang dikunci secara independen. Jangan jangan cukup tambahkan kolom tambahan untuk penghitung baru.
Fungsi ini tidak menggunakan kembali ID yang dihapus, hanya untuk menghindari munculnya celah.
Untuk menggunakan kembali ID saya menyarankan ... tidak menggunakan kembali ID.
Jika Anda benar-benar harus, Anda dapat melakukannya dengan menambahkan ON INSERT OR UPDATE OR DELETE
pemicu di tabel minat yang menambahkan ID yang dihapus ke tabel samping daftar gratis, dan menghapusnya dari tabel daftar gratis saat mereka INSERT
ed. Perlakukan UPDATE
sebagai DELETE
diikuti dengan INSERT
. Sekarang ubah fungsi pembuatan ID di atas sehingga melakukan SELECT free_id INTO next_value FROM free_ids FOR UPDATE LIMIT 1
dan jika ditemukan, DELETE
s baris itu. IF NOT FOUND
mendapat ID baru dari tabel generator seperti biasa. Berikut adalah ekstensi yang belum diuji dari fungsi sebelumnya untuk mendukung penggunaan kembali:
CREATE OR REPLACE FUNCTION get_next_id_reuse(countertable regclass, countercolumn text, freelisttable regclass, freelistcolumn text) RETURNS integer AS $$
DECLARE
next_value integer;
BEGIN
EXECUTE format('SELECT %I FROM %s FOR UPDATE LIMIT 1', freelistcolumn, freelisttable) INTO next_value;
IF next_value IS NOT NULL THEN
EXECUTE format('DELETE FROM %s WHERE %I = %L', freelisttable, freelistcolumn, next_value);
ELSE
EXECUTE format('UPDATE %s SET %I = %I + 1 RETURNING %I', countertable, countercolumn, countercolumn, countercolumn) INTO next_value;
END IF;
RETURN next_value;
END;
$$ LANGUAGE plpgsql;