Ini adalah keenam kalinya saya mencoba untuk mengajukan pertanyaan ini dan itu juga yang terpendek. Semua upaya sebelumnya dihasilkan dengan sesuatu yang lebih mirip dengan posting blog daripada pertanyaan itu sendiri, tetapi saya jamin bahwa masalah saya nyata, hanya saja itu menyangkut satu subjek besar dan tanpa semua detail yang berisi pertanyaan ini akan menjadi tidak jelas apa masalah saya. Jadi begini ...
Abstrak
Saya memiliki database, ini memungkinkan menyimpan data dengan cara yang agak mewah dan menyediakan beberapa fitur non-standar yang diperlukan oleh proses bisnis saya. Fitur-fiturnya adalah sebagai berikut:
- Pembaruan / penghapusan non-destruktif dan non-pemblokiran diimplementasikan melalui pendekatan sisipkan saja, yang memungkinkan pemulihan data dan pencatatan otomatis (setiap perubahan dikaitkan dengan pengguna yang melakukan perubahan itu)
- Data multiversion (mungkin ada beberapa versi dari data yang sama)
- Izin tingkat basis data
- Konsistensi akhirnya dengan spesifikasi ACID dan pembuatan / pembaruan / penghapusan aman-transaksi
- Kemampuan untuk memundurkan atau memajukan tampilan data Anda saat ini ke titik waktu mana pun.
Mungkin ada fitur lain yang saya lupa sebutkan.
Struktur basis data
Semua data pengguna disimpan dalam Itemstabel sebagai string yang dikodekan JSON ( ntext). Semua operasi basis data dilakukan melalui dua prosedur tersimpan GetLatestdan InsertSnashot, mereka memungkinkan untuk beroperasi pada data yang mirip dengan bagaimana GIT mengoperasikan file sumber.
Data yang dihasilkan terhubung (BERGABUNG) di frontend ke dalam grafik yang terhubung penuh sehingga tidak perlu membuat query database dalam banyak kasus.
Dimungkinkan juga untuk menyimpan data dalam kolom SQL biasa alih-alih menyimpannya dalam bentuk penyandian Json. Namun itu meningkatkan ketegangan kompleksitas keseluruhan.
Membaca data
GetLatesthasil dengan data dalam bentuk instruksi, pertimbangkan diagram berikut untuk penjelasan:
Diagram menunjukkan evolusi perubahan yang pernah dibuat ke satu rekaman. Panah pada diagram menunjukkan versi berdasarkan di mana pengeditan terjadi (Bayangkan bahwa pengguna memperbarui beberapa data offline, secara paralel dengan pembaruan yang dibuat oleh pengguna online, kasus seperti itu akan menimbulkan konflik, yang pada dasarnya adalah dua versi data bukannya satu).
Jadi memanggil GetLatestdalam rentang waktu input berikut akan menghasilkan dengan versi rekaman berikut:
GetLatest 0, 15 => 1 <= The data is created upon it's first occurance
GetLatest 0, 25 => 2 <= Inserting another version on top of first one overwrites the existing version
GetLatest 0, 30 => 3 <= The overwrite takes place as soon as the data is inserted
GetLatest 0, 45 => 3, 4 <= This is where the conflict is introduced in the system
GetLatest 0, 55 => 4, 5 <= You can still edit all the versions
GetLatest 0, 65 => 4, 6 <= You can still edit all the versions
GetLatest 0, 75 => 4, 6, 7 <= You can also create additional conflicts
GetLatest 0, 85 => 4, 7, 8 <= You can still edit records
GetLatest 0, 95 => 7, 8, 9 <= You can still edit records
GetLatest 0, 105 => 7, 8 <= Inserting a record with `Json` equal to `NULL` means that the record is deleted
GetLatest 0, 115 => 8 <= Deleting the conflicting versions is the only conflict-resolution scenario
GetLatest 0, 125 => 8, X <= The conflict can be based on the version that was already deleted.
GetLatest 0, 135 => 8, Y <= You can delete such version too and both undelete another version on parallel within one Snapshot (or in several Snapshots).
GetLatest 0, 145 => 8 <= You can delete the undeleted versions by inserting NULL.
GetLatest 0, 155 => 8, Z <= You can again undelete twice-deleted versions
GetLatest 0, 165 => 8 <= You can again delete three-times deleted versions
GetLatest 0, 10000 => 8 <= This means that in order to fast-forward view from moment 0 to moment `10000` you just have to expose record 8 to the user.
GetLatest 55, 115 => 8, [Remove 4], [Remove 5] <= At moment 55 there were two versions [4, 5] so in order to fast-forward to moment 115 the user has to delete versions 4 and 5 and introduce version 8. Please note that version 7 is not present in results since at moment 110 it got deleted.
Agar GetLatestmendukung antarmuka efisien seperti setiap record harus berisi atribut layanan khusus BranchId, RecoveredOn, CreatedOn, UpdatedOnPrev, UpdatedOnCurr, UpdatedOnNext, UpdatedOnNextIdyang digunakan oleh GetLatestuntuk mengetahui apakah catatan jatuh memadai ke dalam jangka waktu yang disediakan untuk GetLatestargumen
Memasukkan data
Untuk mendukung konsistensi, keamanan, dan kinerja transaksi, data dimasukkan ke dalam basis data melalui prosedur multistage khusus.
Data hanya dimasukkan ke dalam basis data tanpa dapat ditanyai oleh
GetLatestprosedur yang tersimpan.Data tersedia untuk
GetLatestprosedur tersimpan, data dibuat tersedia dalamdenormalized = 0keadaan dinormalisasi (yaitu ). Sedangkan data dalam keadaan normal, bidang layananBranchId,RecoveredOn,CreatedOn,UpdatedOnPrev,UpdatedOnCurr,UpdatedOnNext,UpdatedOnNextIdsedang dihitung yang benar-benar lambat.Untuk mempercepat, data didenormalisasi segera setelah tersedia untuk
GetLatestprosedur tersimpan.- Karena langkah 1,2,3 dilakukan dalam transaksi yang berbeda, mungkin saja terjadi kegagalan perangkat keras di tengah-tengah setiap operasi. Meninggalkan data dalam kondisi perantara. Situasi seperti itu normal dan bahkan jika itu akan terjadi, data akan disembuhkan dalam
InsertSnapshotpanggilan berikut . Kode untuk bagian ini dapat ditemukan di antara langkah 2 dan 3 dariInsertSnapshotprosedur tersimpan.
- Karena langkah 1,2,3 dilakukan dalam transaksi yang berbeda, mungkin saja terjadi kegagalan perangkat keras di tengah-tengah setiap operasi. Meninggalkan data dalam kondisi perantara. Situasi seperti itu normal dan bahkan jika itu akan terjadi, data akan disembuhkan dalam
Masalah
Fitur baru (diperlukan oleh bisnis) memaksa saya untuk menolak Denormalizerpandangan khusus yang mengikat semua fitur bersama dan digunakan untuk keduanya GetLatestdan InsertSnapshot. Setelah itu saya mulai mengalami masalah kinerja. Jika awalnya SELECT * FROM Denormalizerdieksekusi hanya dalam sepersekian detik jadi sekarang dibutuhkan hampir 5 menit untuk memproses 10.000 catatan.
Saya bukan pro DB dan saya butuh hampir enam bulan hanya untuk keluar dengan struktur database saat ini. Dan saya telah menghabiskan dua minggu pertama untuk melakukan refactor dan kemudian mencoba mencari tahu apa penyebab utama masalah kinerja saya. Saya tidak bisa menemukannya. Saya menyediakan cadangan basis data (yang dapat Anda temukan di sini) karena skema (dengan semua indeks) agak besar untuk masuk ke SqlFiddle, basis data juga berisi data usang (10.000 catatan) yang saya gunakan untuk tujuan pengujian . Saya juga menyediakan teks untuk Denormalizertampilan yang telah di refactored dan menjadi sangat lambat:
ALTER VIEW [dbo].[Denormalizer]
AS
WITH Computed AS
(
SELECT currItem.Id,
nextOperation.id AS NextId,
prevOperation.FinishedOn AS PrevComputed,
currOperation.FinishedOn AS CurrComputed,
nextOperation.FinishedOn AS NextComputed
FROM Items currItem
INNER JOIN dbo.Operations AS currOperation ON currItem.OperationId = currOperation.Id
LEFT OUTER JOIN dbo.Items AS prevItem ON currItem.PreviousId = prevItem.Id
LEFT OUTER JOIN dbo.Operations AS prevOperation ON prevItem.OperationId = prevOperation.Id
LEFT OUTER JOIN
(
SELECT MIN(I.id) as id, S.PreviousId, S.FinishedOn
FROM Items I
INNER JOIN
(
SELECT I.PreviousId, MIN(nxt.FinishedOn) AS FinishedOn
FROM dbo.Items I
LEFT OUTER JOIN dbo.Operations AS nxt ON I.OperationId = nxt.Id
GROUP BY I.PreviousId
) AS S ON I.PreviousId = S.PreviousId
GROUP BY S.PreviousId, S.FinishedOn
) AS nextOperation ON nextOperation.PreviousId = currItem.Id
WHERE currOperation.Finished = 1 AND currItem.Denormalized = 0
),
RecursionInitialization AS
(
SELECT currItem.Id,
currItem.PreviousId,
currItem.UUID,
currItem.Json,
currItem.TableName,
currItem.OperationId,
currItem.PermissionId,
currItem.Denormalized,
currItem.Id AS BranchID,
COALESCE (C.PrevComputed, C.CurrComputed) AS CreatedOn,
COALESCE (C.PrevComputed, CAST(0 AS BIGINT)) AS RecoveredOn,
COALESCE (C.PrevComputed, CAST(0 AS BIGINT)) AS UpdatedOnPrev,
C.CurrComputed AS UpdatedOnCurr,
COALESCE (C.NextComputed, CAST(8640000000000000 AS BIGINT)) AS UpdatedOnNext,
C.NextId AS UpdatedOnNextId,
0 AS RecursionLevel
FROM Items AS currItem
INNER JOIN Computed AS C ON currItem.Id = C.Id
WHERE currItem.Denormalized = 0
UNION ALL
SELECT currItem.Id,
currItem.PreviousId,
currItem.UUID,
currItem.Json,
currItem.TableName,
currItem.OperationId,
currItem.PermissionId,
currItem.Denormalized,
currItem.BranchId,
currItem.CreatedOn,
currItem.RecoveredOn,
currItem.UpdatedOnPrev,
currItem.UpdatedOnCurr,
currItem.UpdatedOnNext,
currItem.UpdatedOnNextId,
0 AS RecursionLevel
FROM Items AS currItem
WHERE currItem.Denormalized = 1
),
Recursion AS
(
SELECT *
FROM RecursionInitialization AS currItem
UNION ALL
SELECT currItem.Id,
currItem.PreviousId,
currItem.UUID,
currItem.Json,
currItem.TableName,
currItem.OperationId,
currItem.PermissionId,
currItem.Denormalized,
CASE
WHEN prevItem.UpdatedOnNextId = currItem.Id
THEN prevItem.BranchID
ELSE currItem.Id
END AS BranchID,
prevItem.CreatedOn AS CreatedOn,
CASE
WHEN prevItem.Json IS NULL
THEN CASE
WHEN currItem.Json IS NULL
THEN prevItem.RecoveredOn
ELSE C.CurrComputed
END
ELSE prevItem.RecoveredOn
END AS RecoveredOn,
prevItem.UpdatedOnCurr AS UpdatedOnPrev,
C.CurrComputed AS UpdatedOnCurr,
COALESCE (C.NextComputed, CAST(8640000000000000 AS BIGINT)) AS UpdatedOnNext,
C.NextId,
prevItem.RecursionLevel + 1 AS RecursionLevel
FROM Items currItem
INNER JOIN Computed C ON currItem.Id = C.Id
INNER JOIN Recursion AS prevItem ON currItem.PreviousId = prevItem.Id
WHERE currItem.Denormalized = 0
)
SELECT item.Id,
item.PreviousId,
item.UUID,
item.Json,
item.TableName,
item.OperationId,
item.PermissionId,
item.Denormalized,
item.BranchID,
item.CreatedOn,
item.RecoveredOn,
item.UpdatedOnPrev,
item.UpdatedOnCurr,
item.UpdatedOnNext,
item.UpdatedOnNextId
FROM Recursion AS item
INNER JOIN
(
SELECT Id, MAX(RecursionLevel) AS Recursion
FROM Recursion AS item
GROUP BY Id
) AS nested ON item.Id = nested.Id AND item.RecursionLevel = nested.Recursion
GO
Pertanyaan-pertanyaan)
Ada dua skenario yang dipertimbangkan, kasus terdenormalisasi dan normal:
Melihat ke cadangan asli, apa yang membuat
SELECT * FROM Denormalizersangat lambat, saya merasa ada masalah dengan bagian rekursif dari pandangan Denormalizer, saya sudah mencoba membatasidenormalized = 1tetapi tindakan saya tidak mempengaruhi kinerja.Setelah menjalankan
UPDATE Items SET Denormalized = 0hal itu akan membuatGetLatestdanSELECT * FROM Denormalizerlari ke (awalnya dianggap) skenario lambat, apakah ada cara untuk mempercepat hal-hal ketika kita menghitung bidang layananBranchId,RecoveredOn,CreatedOn,UpdatedOnPrev,UpdatedOnCurr,UpdatedOnNext,UpdatedOnNextId
Terima kasih sebelumnya
PS
Saya mencoba untuk tetap berpegang pada SQL standar untuk membuat kueri mudah dibawa-bawa ke database lain seperti MySQL / Oracle / SQLite untuk masa depan, tetapi jika tidak ada sql standar yang mungkin membantu saya baik-baik saja dengan tetap berpegang pada konstruksi spesifik-basis data.


