Đây là lần thứ sáu tôi cố gắng hỏi câu hỏi này và nó cũng là câu hỏi ngắn nhất. Tất cả những nỗ lực trước đó đều dẫn đến một cái gì đó tương tự như một bài đăng trên blog hơn là chính câu hỏi, nhưng tôi đảm bảo với bạn rằng vấn đề của tôi là có thật, chỉ là nó liên quan đến một chủ đề lớn và không có tất cả những chi tiết mà câu hỏi này chứa. không rõ vấn đề của tôi là gì Vì vậy, ở đây đi ...
trừu tượng
Tôi có một cơ sở dữ liệu, nó cho phép lưu trữ dữ liệu theo cách lạ mắt và cung cấp một số tính năng không chuẩn được yêu cầu trong quy trình kinh doanh của tôi. Các tính năng như sau:
- Các cập nhật / xóa không phá hủy và không chặn được triển khai thông qua phương pháp chỉ chèn, cho phép khôi phục dữ liệu và ghi nhật ký tự động (mỗi thay đổi được gắn với người dùng đã thực hiện thay đổi đó)
- Dữ liệu đa biến (có thể có một số phiên bản của cùng một dữ liệu)
- Cấp quyền cơ sở dữ liệu
- Tính nhất quán cuối cùng với đặc tả ACID và tạo / cập nhật / xóa an toàn giao dịch
- Khả năng tua lại hoặc chuyển tiếp nhanh chế độ xem dữ liệu hiện tại của bạn đến bất kỳ thời điểm nào.
Có thể có các tính năng khác mà tôi đã quên đề cập.
Cấu trúc cơ sở dữ liệu
Tất cả dữ liệu người dùng được lưu trữ trong Items
bảng dưới dạng chuỗi được mã hóa JSON ( ntext
). Tất cả các hoạt động cơ sở dữ liệu được thực hiện thông qua hai thủ tục được lưu trữ GetLatest
và InsertSnashot
chúng cho phép hoạt động trên dữ liệu tương tự như cách GIT vận hành các tệp nguồn.
Dữ liệu kết quả được liên kết (THAM GIA) trên frontend thành biểu đồ được liên kết đầy đủ, do đó không cần thực hiện các truy vấn cơ sở dữ liệu trong hầu hết các trường hợp.
Cũng có thể lưu trữ dữ liệu trong các cột SQL thông thường thay vì lưu trữ chúng ở dạng được mã hóa Json. Tuy nhiên, điều đó làm tăng sự phức tạp tổng thể.
Đọc dữ liệu
GetLatest
kết quả với dữ liệu dưới dạng hướng dẫn, xem xét sơ đồ sau để giải thích:
Biểu đồ cho thấy một sự tiến hóa của những thay đổi đã từng được thực hiện cho một bản ghi. Các mũi tên trên sơ đồ hiển thị phiên bản dựa trên đó đã xảy ra chỉnh sửa (Hãy tưởng tượng rằng người dùng đang cập nhật một số dữ liệu ngoại tuyến, song song với các bản cập nhật được tạo bởi người dùng trực tuyến, trường hợp đó sẽ gây ra xung đột, về cơ bản là hai phiên bản dữ liệu thay vì một).
Vì vậy, việc gọi GetLatest
trong khoảng thời gian đầu vào sau sẽ dẫn đến các phiên bản ghi sau:
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.
Để GetLatest
hỗ trợ giao diện hiệu quả như vậy mỗi bản ghi phải chứa các thuộc tính dịch vụ đặc biệt BranchId
, RecoveredOn
, CreatedOn
, UpdatedOnPrev
, UpdatedOnCurr
, UpdatedOnNext
, UpdatedOnNextId
được sử dụng bởi GetLatest
để tìm hiểu xem kỷ lục rơi đầy đủ vào khoảng thời gian quy định GetLatest
đối số
Chèn dữ liệu
Để hỗ trợ tính nhất quán cuối cùng, an toàn và hiệu suất giao dịch, dữ liệu được chèn vào cơ sở dữ liệu thông qua thủ tục đa tầng đặc biệt.
Dữ liệu chỉ được chèn vào cơ sở dữ liệu mà không có khả năng được truy vấn bởi
GetLatest
thủ tục được lưu trữ.Dữ liệu được cung cấp cho
GetLatest
thủ tục được lưu trữ, dữ liệu được cung cấp ởdenormalized = 0
trạng thái chuẩn hóa (nghĩa là ). Trong khi dữ liệu ở trạng thái bình thường, các lĩnh vực dịch vụBranchId
,RecoveredOn
,CreatedOn
,UpdatedOnPrev
,UpdatedOnCurr
,UpdatedOnNext
,UpdatedOnNextId
đang được tính là rất chậm.Để tăng tốc độ, dữ liệu đang được chuẩn hóa ngay khi có sẵn cho
GetLatest
thủ tục được lưu trữ.- Vì các bước 1,2,3 được thực hiện trong các giao dịch khác nhau, có thể xảy ra lỗi phần cứng ở giữa mỗi thao tác. Để lại dữ liệu ở trạng thái trung gian. Tình huống như vậy là bình thường và ngay cả khi nó sẽ xảy ra, dữ liệu sẽ được chữa lành trong
InsertSnapshot
cuộc gọi sau . Mã cho phần này có thể được tìm thấy ở giữa bước 2 và 3 củaInsertSnapshot
thủ tục được lưu trữ.
- Vì các bước 1,2,3 được thực hiện trong các giao dịch khác nhau, có thể xảy ra lỗi phần cứng ở giữa mỗi thao tác. Để lại dữ liệu ở trạng thái trung gian. Tình huống như vậy là bình thường và ngay cả khi nó sẽ xảy ra, dữ liệu sẽ được chữa lành trong
Vấn đề
Một tính năng mới (được yêu cầu bởi doanh nghiệp) buộc tôi phải cấu trúc lại Denormalizer
chế độ xem đặc biệt liên kết tất cả các tính năng với nhau và được sử dụng cho cả hai GetLatest
và InsertSnapshot
. Sau đó tôi đã bắt đầu gặp vấn đề về hiệu suất. Nếu ban đầu được SELECT * FROM Denormalizer
thực hiện chỉ trong phân số thứ hai thì bây giờ phải mất gần 5 phút để xử lý 10000 hồ sơ.
Tôi không phải là DB pro và tôi đã mất gần sáu tháng để tìm ra cấu trúc cơ sở dữ liệu hiện tại. Và tôi đã dành hai tuần đầu tiên để thực hiện các phép tái cấu trúc và sau đó cố gắng tìm ra nguyên nhân gốc rễ cho vấn đề hiệu suất của tôi. Tôi không thể tìm thấy nó. Tôi đang cung cấp sao lưu cơ sở dữ liệu (mà bạn có thể tìm thấy ở đây) vì lược đồ (với tất cả các chỉ mục) khá lớn để phù hợp với SqlFiddle, cơ sở dữ liệu cũng chứa dữ liệu lỗi thời (hơn 10000 bản ghi) mà tôi đang sử dụng cho mục đích thử nghiệm . Ngoài ra, tôi đang cung cấp văn bản cho Denormalizer
chế độ xem được tái cấu trúc và trở nên chậm chạp một cách đau đớn:
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
Câu hỏi
Có hai kịch bản được xem xét, các trường hợp không chuẩn hóa và chuẩn hóa:
Nhìn vào bản sao lưu gốc, điều khiến cho
SELECT * FROM Denormalizer
quá chậm, tôi cảm thấy có vấn đề với phần đệ quy của chế độ xem Không bình thường, tôi đã thử hạn chếdenormalized = 1
nhưng không phải hành động của tôi ảnh hưởng đến hiệu suất.Sau khi chạy
UPDATE Items SET Denormalized = 0
nó sẽ làmGetLatest
vàSELECT * FROM Denormalizer
chạy vào (ban đầu được cho là) kịch bản chậm, là có một cách để mọi thứ tăng tốc khi chúng ta đang tính toán các lĩnh vực dịch vụBranchId
,RecoveredOn
,CreatedOn
,UpdatedOnPrev
,UpdatedOnCurr
,UpdatedOnNext
,UpdatedOnNextId
Cảm ơn bạn trước
PS
Tôi đang cố gắng bám sát SQL tiêu chuẩn để giúp truy vấn dễ dàng di chuyển đến các cơ sở dữ liệu khác như MySQL / Oracle / SQLite trong tương lai, nhưng nếu không có sql tiêu chuẩn nào có thể giúp tôi ổn với việc gắn bó với các cấu trúc cụ thể của cơ sở dữ liệu.