Giới thiệu
Gần đây, chúng tôi đã gặp phải sự cố hiệu suất thú vị trên một trong các cơ sở dữ liệu SQL Server của chúng tôi, xử lý các giao dịch với tốc độ nghiêm trọng. Bảng giao dịch được sử dụng để nắm bắt các giao dịch này đã trở thành một bảng nóng. Kết quả là sự cố xuất hiện trong lớp ứng dụng. Đó là thời gian chờ không liên tục của phiên tìm cách đăng giao dịch.
Điều này xảy ra bởi vì một phiên thường sẽ "giữ" bảng và gây ra một loạt các khóa giả trong cơ sở dữ liệu.
Phản ứng đầu tiên của một quản trị viên cơ sở dữ liệu điển hình là xác định phiên chặn chính và chấm dứt nó một cách an toàn. Điều này an toàn vì nó thường là một câu lệnh SELECT hoặc một phiên không hoạt động.
Cũng có những nỗ lực khác để giải quyết vấn đề:
- Xóa bảng. Điều này được kỳ vọng sẽ mang lại hiệu suất tốt ngay cả khi truy vấn phải quét toàn bộ bảng.
- Bật mức cách ly READ COMMITTED SNAPSHOT để giảm tác động của các phiên chặn.
Trong bài viết này, chúng tôi sẽ cố gắng tạo lại một phiên bản đơn giản của kịch bản và sử dụng nó để cho thấy cách lập chỉ mục đơn giản có thể giải quyết các tình huống như thế này khi được thực hiện đúng cách.
Hai bảng liên quan
Hãy xem Liệt kê 1 và Liệt kê 2. Chúng hiển thị các phiên bản đơn giản hóa của các bảng liên quan đến kịch bản đang được xem xét.
-- Listing 1: Create TranLog Table
use DB2
go
create table TranLog (
TranID INT IDENTITY(1,1)
,CustomerID char(4)
,ProductCount INT
,TotalPrice Money
,TranTime Timestamp
)
-- Listing 2: Create TranDetails Table
use DB2
go
create table TranDetails (
TranDetailsID INT IDENTITY(1,1)
,TranID INT
,ProductCode uniqueidentifier
,UnitCost Money
,ProductCount INT
,TotalPrice Money
)
Liệt kê 3 hiển thị một trình kích hoạt chèn bốn hàng vào TranDetails bảng cho mọi hàng được chèn trong TranLog bảng.
-- Listing 3: Create Trigger
CREATE TRIGGER dbo.GenerateDetails
ON dbo.TranLog
AFTER INSERT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
insert into dbo.TranDetails (TranID, ProductCode,UnitCost, ProductCount, TotalPrice)
select top 1 dbo.TranLog.TranID, NEWID(), dbo.TranLog.TotalPrice/dbo.TranLog.ProductCount, dbo.TranLog.ProductCount, dbo.TranLog.TotalPrice
from dbo.TranLog order by TranID desc;
insert into dbo.TranDetails (TranID, ProductCode,UnitCost, ProductCount, TotalPrice)
select top 1 dbo.TranLog.TranID, NEWID(), dbo.TranLog.TotalPrice/dbo.TranLog.ProductCount, dbo.TranLog.ProductCount, dbo.TranLog.TotalPrice
from dbo.TranLog order by TranID desc;
insert into dbo.TranDetails (TranID, ProductCode,UnitCost, ProductCount, TotalPrice)
select top 1 dbo.TranLog.TranID, NEWID(), dbo.TranLog.TotalPrice/dbo.TranLog.ProductCount, dbo.TranLog.ProductCount, dbo.TranLog.TotalPrice
from dbo.TranLog order by TranID desc;
insert into dbo.TranDetails (TranID, ProductCode,UnitCost, ProductCount, TotalPrice)
select top 1 dbo.TranLog.TranID, NEWID(), dbo.TranLog.TotalPrice/dbo.TranLog.ProductCount, dbo.TranLog.ProductCount, dbo.TranLog.TotalPrice
from dbo.TranLog order by TranID desc;
END
GO
Tham gia truy vấn
Đó là điển hình để tìm các bảng giao dịch được hỗ trợ bởi các bảng lớn. Mục đích là để giữ các giao dịch cũ hơn nhiều hoặc để lưu trữ chi tiết của các bản ghi được tóm tắt trong bảng đầu tiên. Hãy coi đây là đơn đặt hàng và chi tiết đơn hàng các bảng điển hình trong cơ sở dữ liệu mẫu SQL Server. Trong trường hợp của chúng tôi, chúng tôi đang xem xét TranLog và TranDetails bảng.
Trong các trường hợp bình thường, các giao dịch sẽ cư trú trên hai bảng này theo thời gian. Về báo cáo hoặc truy vấn đơn giản, truy vấn sẽ thực hiện phép nối trên hai bảng này. Phép nối này sẽ viết hoa trên một cột chung giữa các bảng.
Đầu tiên, chúng tôi điền bảng bằng cách sử dụng truy vấn trong Liệt kê 4.
-- Listing 4: Insert Rows in TranLog
use DB2
go
insert into TranLog values ('CU01', 5, '50.45', DEFAULT);
insert into TranLog values ('CU02', 7, '42.35', DEFAULT);
insert into TranLog values ('CU03', 15, '39.55', DEFAULT);
insert into TranLog values ('CU04', 9, '33.50', DEFAULT);
insert into TranLog values ('CU05', 2, '105.45', DEFAULT);
go 1000
use DB2
go
select * from TranLog;
select * from TranDetails;
Trong mẫu của chúng tôi, cột phổ biến được liên kết sử dụng là TranID cột:
-- Listing 5 Join Query
-- 5a
select * from TranLog a join TranDetails b
on a.TranID=b.TranID where a.CustomerID='CU03';
-- 5b
select * from TranLog a join TranDetails b
on a.TranID=b.TranID where a.TranID=30;
Bạn có thể xem hai truy vấn mẫu đơn giản sử dụng phép nối để truy xuất bản ghi từ TranLog và TranDetails .
Khi chúng tôi chạy các truy vấn trong Liệt kê 5, trong cả hai trường hợp, chúng tôi phải thực hiện quét toàn bộ bảng trên cả hai bảng (xem Hình 1 và 2). Phần chi phối của mỗi truy vấn là các hoạt động vật lý. Cả hai đều là liên kết bên trong. Tuy nhiên, Liệt kê 5a sử dụng Kết hợp băm tham gia, trong khi Liệt kê 5b sử dụng Vòng lặp lồng nhau tham gia. Lưu ý:Liệt kê 5a trả về 4000 hàng trong khi Liệt kê 4b trả về 4 hàng.
Ba bước điều chỉnh hiệu suất
Tối ưu hóa đầu tiên mà chúng tôi thực hiện là giới thiệu một chỉ mục (chính xác là khóa chính) trên TranID cột của TranLog bảng:
-- Listing 6: Create Primary Key
alter table TranLog add constraint PK_TranLog primary key clustered (TranID);
Hình 3 và 4 cho thấy SQL Server sử dụng chỉ mục này trong cả hai truy vấn, thực hiện quét trong Liệt kê 5a và tìm kiếm trong Liệt kê 5b.
Chúng tôi có một tìm kiếm chỉ mục trong Liệt kê 5b. Nó xảy ra vì cột liên quan đến vị ngữ mệnh đề WHERE - TranID. Đó là cột mà chúng tôi đã áp dụng một chỉ mục.
Tiếp theo, chúng tôi giới thiệu khóa ngoại trên TranID cột TranDetails bảng (Liệt kê 7).
-- Listing 7: Create Foreign Key
alter table TranDetails add constraint FK_TranDetails foreign key (TranID) references TranLog (TranID);
Điều này không thay đổi nhiều trong kế hoạch thực hiện. Tình hình hầu như giống như được trình bày trước đó trong Hình 3 và 4.
Sau đó, chúng tôi giới thiệu một chỉ mục trên cột khóa ngoại:
-- Listing 8: Create Index on Foreign Key
create index IX_TranDetails on TranDetails (TranID);
Hành động này thay đổi đáng kể kế hoạch thực thi của Liệt kê 5b (Xem Hình 6). Chúng tôi thấy nhiều tìm kiếm chỉ mục hơn sẽ xảy ra. Ngoài ra, hãy lưu ý tra cứu RID trong Hình 6.
Tra cứu RID trên heap thường xảy ra khi không có khóa chính. Một heap là một bảng không có khóa chính.
Cuối cùng, chúng tôi thêm khóa chính vào TranDetails bàn. Thao tác này sẽ loại bỏ tính năng quét bảng và tra cứu đống RID trong Liệt kê 5a và 5b tương ứng (Xem Hình 7 và 8).
-- Listing 9: Create Primary Key on TranDetailsID
alter table TranDetails add constraint PK_TranDetails primary key clustered (TranDetailsID);
Kết luận
Việc cải thiện hiệu suất được giới thiệu bởi các chỉ mục đã nổi tiếng đối với ngay cả những người mới làm quen với DBA. Tuy nhiên, chúng tôi muốn chỉ ra rằng bạn cần xem xét kỹ cách truy vấn sử dụng chỉ mục.
Hơn nữa, ý tưởng là thiết lập giải pháp trong trường hợp cụ thể mà chúng tôi có các truy vấn kết hợp giữa Nhật ký giao dịch bảng và Chi tiết giao dịch bảng.
Nói chung, việc thực thi mối quan hệ giữa các bảng như vậy bằng cách sử dụng một khóa và giới thiệu các chỉ mục cho các cột khóa chính và khóa ngoại là rất hợp lý.
Trong việc phát triển các ứng dụng sử dụng thiết kế như vậy, các nhà phát triển nên ghi nhớ các chỉ mục và mối quan hệ bắt buộc ở giai đoạn thiết kế. Các công cụ hiện đại dành cho các chuyên gia SQL Server làm cho các yêu cầu này được thực hiện dễ dàng hơn nhiều. Bạn có thể lập hồ sơ truy vấn của mình bằng công cụ Trình biên dịch truy vấn chuyên biệt. Đây là một phần của giải pháp chuyên nghiệp đa tính năng dbForge Studio dành cho SQL Server do Devart phát triển để làm cho cuộc sống của DBA trở nên đơn giản hơn.