Bu altıncı kez bu soruyu sormaya çalışıyorum ve bu da en kısa olanı. Önceki tüm denemeler, sorunun kendisinden ziyade bir blog yayınına daha benzer bir şeyle sonuçlandı, ancak size sorunumun gerçek olduğunu garanti ediyorum, sadece büyük bir konuyu ilgilendiriyor ve bu sorunun içerdiği tüm ayrıntılar olmadan sorunumun ne olduğu belli değil. İşte gidiyor ...
Öz
Ben bir veritabanı var, bu tür süslü bir şekilde veri depolamak sağlar ve benim iş süreci için gerekli standart dışı birkaç özellik sağlar. Özellikler aşağıdaki gibidir:
- Veri kurtarma ve otomatik günlüğe kaydetmeye olanak tanıyan, yalnızca ekleme yaklaşımıyla uygulanan, tahribatsız ve engellemeyen güncellemeler / silmeler (her değişiklik, bu değişikliği yapan kullanıcıya bağlıdır)
- Çoklu sürüm verileri (aynı verilerin birkaç sürümü olabilir)
- Veritabanı düzeyinde izinler
- ASİT spesifikasyonu ve işlem güvenli ile nihai tutarlılık oluşturur / günceller / siler
- Mevcut veri görünümünüzü istediğiniz zaman geri sarma veya hızlı ileri sarma yeteneği.
Bahsetmeyi unuttuğum başka özellikler de olabilir.
Veritabanı yapısı
Tüm kullanıcı verileri Items
tabloda JSON kodlu dize ( ntext
) olarak depolanır . Tüm veritabanı işlemleri iki saklı yordam aracılığıyla gerçekleştirilir GetLatest
ve InsertSnashot
GIT'in kaynak dosyaları nasıl kullandığına benzer veriler üzerinde çalışmasına izin verir.
Ortaya çıkan veriler, ön uçta tamamen bağlı grafiğe bağlanır (JOINed), bu nedenle çoğu durumda veritabanı sorguları yapmaya gerek yoktur.
Verileri Json kodlu formda saklamak yerine normal SQL sütunlarında saklamak da mümkündür. Ancak bu, genel karmaşıklık suşunu arttırır.
Verileri okuma
GetLatest
talimat şeklinde veriler içeren sonuçlar, açıklama için aşağıdaki diyagramı göz önünde bulundurun :
Diyagram, tek bir kayıtta yapılan değişikliklerin evrimini göstermektedir. Diyagramdaki oklar, düzenlemenin gerçekleştiği sürümü gösterir (Kullanıcının, çevrimiçi kullanıcı tarafından yapılan güncellemelere paralel olarak bazı verileri çevrimdışı güncellediğini hayal edin, bu durumda temelde iki veri sürümü olan çakışma ortaya çıkar. yerine).
Dolayısıyla, GetLatest
aşağıdaki girdi zaman aralıklarında çağrı yapmak aşağıdaki kayıt sürümleriyle sonuçlanır:
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.
İçin için GetLatest
her kayıt özel servis özelliklerini içermelidir böyle verimli bir arayüz desteklemek için BranchId
, RecoveredOn
, CreatedOn
, UpdatedOnPrev
, UpdatedOnCurr
, UpdatedOnNext
, UpdatedOnNextId
tarafından kullanıldığını GetLatest
rekor için sağlanan zaman içine yeterince düşer olmadığını anlamaya GetLatest
argümanlar
Veri ekleme
Nihai tutarlılığı, işlem güvenliğini ve performansını desteklemek için veriler özel çok aşamalı prosedürle veritabanına eklenir.
Veriler,
GetLatest
saklı yordam tarafından sorgulanamadan veritabanına eklenir .Veriler
GetLatest
saklı yordam için kullanılabilir hale getirilir , veriler normalleştirilmiş (yanidenormalized = 0
) durumda kullanılabilir hale getirilir . Veri normalize halde iken, hizmet alanlarıBranchId
,RecoveredOn
,CreatedOn
,UpdatedOnPrev
,UpdatedOnCurr
,UpdatedOnNext
,UpdatedOnNextId
gerçekten yavaş olduğu hesaplanıyor.İşleri hızlandırmak için, veriler
GetLatest
saklı yordam için kullanılabilir hale gelir gelmez normalleştiriliyor .- Adım 1,2,3 farklı işlemler içinde gerçekleştirildiğinden, her işlemin ortasında bir donanım arızası meydana gelebilir. Verileri ara durumda bırakma. Bu durum normaldir ve gerçekleşecek olsa bile, veriler aşağıdaki
InsertSnapshot
çağrıda iyileşir . Bu parçanın kodu,InsertSnapshot
saklı yordamın 2. ve 3. adımları arasında bulunabilir .
- Adım 1,2,3 farklı işlemler içinde gerçekleştirildiğinden, her işlemin ortasında bir donanım arızası meydana gelebilir. Verileri ara durumda bırakma. Bu durum normaldir ve gerçekleşecek olsa bile, veriler aşağıdaki
Sorun
(İşletme tarafından gerekli) yeni özellikler özel planı ayrı zorladı Denormalizer
bağları-up hep birlikte özellikleri ve her ikisi için de kullanılır görünümü GetLatest
ve InsertSnapshot
. Bundan sonra performans sorunları yaşamaya başladım. Başlangıçta SELECT * FROM Denormalizer
sadece ikinci kesirlerde yürütülürse, şimdi 10000 kayıt işlemek yaklaşık 5 dakika sürer.
Ben bir DB profesyonel değilim ve sadece mevcut veritabanı yapısı ile çıkmak yaklaşık altı ay sürdü. Yeniden düzenleme yapmak için önce iki hafta geçirdim ve ardından performans sorunumun temel nedenini anlamaya çalıştım. Sadece bulamıyorum. Şema (tüm dizinleri ile) SqlFiddle sığacak kadar büyük olduğundan veritabanı yedekleme (burada bulabilirsiniz) veriyorum, veritabanı ayrıca test amaçlı kullandığım eski veri (10000+ kayıt) içerir . Ayrıca Denormalizer
yeniden düzenlenmiş ve acı yavaş yavaş görünüm için metin sağlıyorum :
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
Sorular)
Göz önünde bulundurulması gereken iki senaryo vardır: denormalize ve normalleştirilmiş vakalar:
Orijinal yedeklemeye bakarak, bu
SELECT * FROM Denormalizer
kadar acı verici olanı yavaşlatan, Denormalizer görünümünün özyinelemeli kısmında bir sorun var gibi hissediyorum, kısıtlamayı denedimdenormalized = 1
ama eylemlerimin hiçbiri performansı etkilemedi.Çalıştırdıktan sonra
UPDATE Items SET Denormalized = 0
onu yaparGetLatest
veSELECT * FROM Denormalizer
çalışma içine yavaş senaryo (başlangıçta olduğu düşünülen), hız şeyler kadar biz hizmet alanlarını hesaplıyoruz için bir yol varBranchId
,RecoveredOn
,CreatedOn
,UpdatedOnPrev
,UpdatedOnCurr
,UpdatedOnNext
,UpdatedOnNextId
Şimdiden teşekkür ederim
PS
Sorgu gelecek için MySQL / Oracle / SQLite gibi diğer veritabanlarına kolayca taşınabilir hale getirmek için standart SQL sopa çalışıyorum, ama veritabanına özgü yapılara yapışmasını Tamam ben yardımcı olabilir standart sql varsa.