Gunakan database untuk melacak penguncian / pembukaan objek

8

Saya memiliki persyaratan untuk melacak tindakan mengunci / membuka kunci objek. Sebelum tindakan apa pun dilakukan pada suatu objek (kontrak, mitra, dll), suatu lockperistiwa dikeluarkan. Setelah tindakan selesai, itu mengeluarkan unlockacara.

Saya ingin mendapatkan benda-benda yang terkunci tetapi belum dibuka. Tujuannya adalah untuk membuat kueri cepat dan untuk menghindari kebuntuan.

Di bawah ini adalah tabelnya

create table locks (
    id int identity,
    name varchar(255),
    lock int
) 

insert into locks values('a', 1)
insert into locks values('b', 1)

insert into locks values('c', 1)
insert into locks values('d', 1)

insert into locks values('a', 0)
insert into locks values('c', 0)


insert into locks values('a', 1)
insert into locks values('b', 1)

Saya menggunakan kueri di bawah ini untuk menolak objek yang belum dibuka:

select distinct m.name from locks m
    where (select COUNT(id) from locks locked
           where locked.lock = 1 and locked.name = m.name)
        >  (select COUNT(id) from locks unlocked
            where unlocked.lock = 0 and unlocked.name = m.name)

Ini berfungsi dengan benar dan hasilnya a, bdan d.

Pertanyaan saya adalah: - Apakah solusi saya cukup memadai untuk menghindari kebuntuan? Apakah ada masalah yang dapat terjadi jika ada banyak INSERTselama eksekusi permintaan? - Apakah Anda punya cara lain (lebih baik) untuk menyelesaikan ini?

MEMPERBARUI

Saya minta maaf karena tidak memasukkan konteks ke dalam pertanyaan. Desain basis data di atas bukan untuk menggantikan penguncian basis data.

Kami memiliki sistem eksternal yang kami sebut dari sistem kami. Ini membutuhkan panggilan lockdan unlockmetode pada sistem mereka sebelum setiap tindakan dilakukan pada suatu objek (bisa berupa kontrak atau mitra).

Baru-baru ini, kami memiliki situasi sedemikian rupa sehingga server lumpuh dan kami harus memulai ulang. Sayangnya, proses yang berjalan yang sudah dipanggil locktidak memiliki kesempatan untuk memanggil unlockuntuk melepaskan objek, sehingga menyebabkan beberapa masalah lain ketika sistem kami terhubung lagi ke yang eksternal.

Jadi kami ingin memberikan kemampuan untuk melacak setiap lockpanggilan. Setelah me-restart server, kita akan memanggil unlockobjek yang sebelumnya terkunci.

Terima kasih Remus Rusanu karena menunjukkan bahwa pertanyaan saya menggunakan prototipe DDL. Ini adalah pertama kalinya saya memposting pertanyaan di DBA dan saya minta maaf karena tidak membaca FAQ.

Terima kasih

Genzer
sumber

Jawaban:

11

Tujuannya adalah untuk membuat kueri cepat dan untuk menghindari kebuntuan.

Ini adalah tujuan yang tidak realistis. Kebuntuan ditentukan oleh aplikasi yang mendapatkan kunci dan tidak bisa mengendalikan bagaimana Anda menerapkan kunci tersebut. Yang terbaik yang bisa Anda harapkan adalah mendeteksi kebuntuan.

Menerapkan kunci karena catatan bermasalah. Baris tetap ada dan Anda akan membocorkan kunci pada crash aplikasi, bahkan ketika implementasinya sempurna . Lakukan kunci dengan applocks . Mereka memiliki semantik transaksional yang jelas dan kebuntuan terdeteksi oleh mesin.

Namun, melihat apa yang ingin Anda capai, kecil kemungkinan Anda membutuhkan kunci sama sekali . Anda sedang menggambarkan antrian (pilih item berikutnya yang tersedia untuk diproses dan hindari konflik => antrian, tidak mengunci). Baca Menggunakan tabel sebagai Antrian .

Adapun implementasi spesifik Anda (menggunakan tabel yang membuat sejarah penguncian) saya harus jujur: itu adalah bencana. Untuk mulai dengan desain tabel benar-benar tidak memadai untuk penggunaan yang dimaksud: Anda meminta nama tetapi tabel adalah tumpukan tanpa indeks. Ini memiliki kolom identitas tanpa alasan yang jelas. Anda dapat menjawab bahwa ini hanyalah tabel 'kode semu', tetapi ini adalah DBA.SE, Anda tidak memposting di sini DDL tidak lengkap!

Tetapi yang lebih penting, implementasi tidak menerapkan penguncian! Tidak ada yang mencegah dua pengguna 'mengunci' objek yang sama dua kali. 'Penguncian' Anda sepenuhnya bergantung pada penelepon yang secara ajaib berlaku benar. Bahkan aplikasi tertulis terbaik tidak dapat menggunakan penguncian ini, karena tidak ada cara untuk memeriksa dan memperoleh kunci secara atomis . Dua pengguna dapat memeriksa, menyimpulkan bahwa 'a' tidak terkunci, dan secara bersamaan memasukkan ('a', 1)catatan. Paling tidak Anda akan membutuhkan kendala yang unik. Yang tentu saja akan mematahkan semantik "menghitung kunci vs membuka kunci untuk menentukan status".

Maaf untuk mengatakan, tetapi ini adalah implementasi kelas F.

MEMPERBARUI

Jadi kami ingin memberikan kemampuan untuk melacak setiap panggilan kunci. Setelah me-restart server, kita akan memanggil unlock pada objek yang sebelumnya terkunci.

Tanpa terlibat dalam transaksi terdistribusi dua fase dengan sistem jarak jauh yang dapat Anda lakukan adalah 'upaya terbaik', karena ada banyak kondisi lomba antara Anda menulis 'membuka' dan benar-benar memanggil 'membuka' pada sistem pihak ketiga . Sebagai upaya terbaik, berikut ini adalah rekomendasi saya:

  • buat tabel sederhana untuk melacak kunci:

    CREATE TABLE locks (name VARCHAR(255) NOT NULL PRIMARY KEY);

  • sebelum memanggil lockmasukkan kunci ke meja Anda dan komit .

  • setelah menelepon unlock hapus kunci dari meja Anda dan komit
  • pada startup sistem lihat tabel. setiap baris ada kunci sisa dari proses sebelumnya dan harus 'tidak dikunci'. Panggil unlocksetiap baris, lalu hapus baris. Hanya setelah semua kunci yang tertunda telah 'dibuka' Anda dapat melanjutkan fungsionalitas normal aplikasi.

Saya mengusulkan ini karena meja akan tetap kecil. Setiap saat itu hanya berisi kunci aktif saat ini. Itu tidak akan tumbuh ad-mual dan menyebabkan masalah di kemudian hari karena ukurannya yang tipis. Sepele untuk melihat apa yang terkunci.

Tentu saja implementasi ini tidak menawarkan riwayat audit tentang apa yang dikunci oleh siapa dan kapan. Anda dapat menambahkan itu jika diperlukan, sebagai tabel yang berbeda, di mana Anda hanya memasukkan acara (mengunci atau membuka kunci) dan tidak pernah menanyakan itu untuk mengetahui kunci 'yatim piatu'.

Anda masih harus siap dengan panggilan untuk 'membuka' untuk gagal selama startup karena Anda tidak dapat menjamin bahwa sistem Anda tidak crash setelah memanggil unlocktetapi sebelum menghapus baris (dengan kata lain meja Anda dan sistem pihak ketiga telah terpisah dan memiliki perbedaan versi kebenaran). Sekali lagi Anda tidak dapat mencegah ini tanpa transaksi terdistribusi dan saya tidak akan pernah menyarankan untuk DTC.

Remus Rusanu
sumber
4

Ini akan mematikan concurrency di aplikasi Anda. SQL Server sudah memiliki semua yang diperlukan untuk menghindari memperbarui baris yang sama pada saat yang sama, jadi sebenarnya tidak perlu melakukan itu.

Kebuntuan dapat terjadi karena beberapa alasan, jadi saya sarankan Anda menyelidiki alasan itu sebelum membangun sistem penguncian Anda sendiri. Anda dapat menangkap grafik kebuntuan dari sesi XE kesehatan sistem dan mulai menganalisisnya.

Biasanya kebuntuan adalah hasil dari mengambil kunci pada objek dalam urutan berbeda, jadi Anda harus mencoba untuk mengambil kunci selalu dalam urutan yang sama. Ada alasan lain mengapa kebuntuan dapat terjadi, tetapi panduan umum adalah bahwa transaksi pendek menahan kunci untuk waktu yang singkat, sehingga menyesuaikan pertanyaan Anda dengan kode yang lebih baik, indeks dan strategi divide-et-impera mungkin akan menjadi cara terbaik untuk menghilangkan kebuntuan.

Kebuntuan "Readers blocking writer" bisa sangat dimitigasi dengan mengalihkan basis data ke Read Committed Snapshot Isolasi . Jika aplikasi Anda dibuat dengan penguncian pesimistis, Anda harus meninjau dan menguji semuanya dengan sangat hati-hati sebelum mengaktifkan opsi ini pada basis data Anda.

Jika Anda bersikeras menuruni rute "sistem penguncian khusus", setidaknya gunakan sesuatu yang menjamin penggunaan kembali kunci jika terjadi sesuatu dengan aplikasi yang salah. Anda mungkin ingin menyelidiki prosedur tersimpan sp_getapplock bawaan , yang melakukan sesuatu yang mirip dengan apa yang Anda cari.

UPDATE: Setelah membaca pertanyaan Anda yang diedit, ini adalah cara alternatif untuk mengekspresikan pertanyaan yang sama:

SELECT *
FROM (
    SELECT *, RN = ROW_NUMBER() OVER(PARTITION BY name ORDER BY id DESC) 
    FROM locks
) AS data
WHERE RN = 1 
    AND lock = 1;

Ini akan berfungsi jika objek dibiarkan dikunci sekali saja, yang tampaknya merupakan inti dari sistem penguncian.

spaghettidba
sumber