Database
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> Database

Tìm hiểu về sự tuôn ra của bộ đệm nhật ký

Có thể bạn đã nghe nhiều lần trước đây rằng SQL Server cung cấp bảo đảm cho các thuộc tính giao dịch ACID. Bài viết này tập trung vào phần D, tất nhiên là viết tắt của độ bền. Cụ thể hơn, bài viết này tập trung vào một khía cạnh của kiến ​​trúc ghi nhật ký SQL Server nhằm thực thi độ bền của giao dịch — bộ đệm nhật ký bị xóa. Tôi nói về chức năng mà bộ đệm nhật ký phục vụ, các điều kiện buộc SQL Server phải chuyển bộ đệm nhật ký vào đĩa, những gì bạn có thể làm để tối ưu hóa hiệu suất giao dịch, cũng như các công nghệ liên quan được bổ sung gần đây như độ bền chậm và bộ nhớ lớp lưu trữ không bay hơi.

Xóa bộ đệm nhật ký

Phần D trong thuộc tính giao dịch ACID là viết tắt của độ bền. Ở cấp độ logic, điều đó có nghĩa là khi một ứng dụng gửi cho SQL Server một hướng dẫn để thực hiện một giao dịch (rõ ràng hoặc với một giao dịch tự động cam kết), SQL Server thường chỉ trả lại quyền kiểm soát cho người gọi sau khi nó có thể đảm bảo rằng giao dịch đó là lâu dài. Nói cách khác, một khi người gọi có lại quyền kiểm soát sau khi thực hiện một giao dịch, nó có thể tin tưởng rằng ngay cả khi một lúc sau máy chủ gặp sự cố mất điện, các thay đổi của giao dịch đã được đưa vào cơ sở dữ liệu. Miễn là máy chủ khởi động lại thành công và các tệp cơ sở dữ liệu không bị hỏng, bạn sẽ thấy rằng tất cả các thay đổi giao dịch đã được áp dụng.

Cách SQL Server thực thi độ bền của giao dịch một phần là bằng cách đảm bảo rằng tất cả các thay đổi của giao dịch đều được ghi vào nhật ký giao dịch của cơ sở dữ liệu trên đĩa trước khi trả lại quyền kiểm soát cho người gọi. Trong trường hợp mất điện sau khi cam kết của giao dịch được xác nhận, bạn biết rằng tất cả những thay đổi đó ít nhất đã được ghi vào nhật ký giao dịch trên đĩa. Đó là trường hợp ngay cả khi các trang dữ liệu liên quan chỉ được sửa đổi trong bộ đệm dữ liệu (vùng đệm) nhưng chưa được chuyển vào tệp dữ liệu trên đĩa. Khi bạn khởi động lại SQL Server, trong giai đoạn làm lại của quá trình khôi phục, SQL Server sử dụng thông tin được ghi trong nhật ký để phát lại các thay đổi được áp dụng sau điểm kiểm tra cuối cùng và chưa thực hiện được đối với tệp dữ liệu. Câu chuyện còn nhiều điều hơn một chút tùy thuộc vào mô hình khôi phục mà bạn đang sử dụng và liệu các hoạt động hàng loạt có được áp dụng sau điểm kiểm tra cuối cùng hay không, nhưng đối với mục đích của cuộc thảo luận của chúng ta, chỉ cần tập trung vào phần liên quan đến việc tăng cường các thay đổi đối với nhật ký giao dịch.

Phần khó khăn trong kiến ​​trúc ghi nhật ký của SQL Server là việc ghi nhật ký diễn ra tuần tự. Nếu SQL Server không sử dụng một số loại bộ đệm nhật ký để giảm bớt việc ghi nhật ký vào đĩa, thì các hệ thống đòi hỏi nhiều ghi - đặc biệt là những hệ thống liên quan đến nhiều giao dịch nhỏ - sẽ nhanh chóng gặp phải tắc nghẽn hiệu suất khủng khiếp liên quan đến ghi nhật ký.

Để giảm bớt tác động tiêu cực đến hiệu suất của việc ghi nhật ký tuần tự thường xuyên vào đĩa, SQL Server sử dụng bộ đệm nhật ký trong bộ nhớ. Việc ghi nhật ký lần đầu tiên được thực hiện vào bộ đệm nhật ký và một số điều kiện nhất định khiến SQL Server xóa hoặc làm cứng bộ đệm nhật ký vào đĩa. Đơn vị được làm cứng (hay còn gọi là khối nhật ký) có thể nằm trong khoảng từ tối thiểu của kích thước ngành (512 byte) đến tối đa là 60 KB. Sau đây là các điều kiện kích hoạt xả bộ đệm nhật ký (bỏ qua các phần xuất hiện trong dấu ngoặc vuông lúc này):

  • SQL Server nhận được yêu cầu cam kết của một giao dịch [hoàn toàn bền] thay đổi dữ liệu [trong cơ sở dữ liệu không phải tempdb]
  • Bộ đệm nhật ký đầy lên, đạt đến dung lượng 60 KB
  • SQL Server cần làm cứng các trang dữ liệu bẩn, ví dụ:trong quá trình kiểm tra và các bản ghi nhật ký đại diện cho các thay đổi đối với các trang đó vẫn chưa được làm cứng ( ghi nhật ký trước hay ngắn gọn là WAL)
  • Bạn yêu cầu thủ công xóa bộ đệm nhật ký bằng cách thực hiện thủ tục sys.sp_flush_log
  • SQL Server ghi một giá trị khôi phục mới liên quan đến bộ đệm ẩn liên quan đến trình tự [trong cơ sở dữ liệu không phải tempdb]

Bốn điều kiện đầu tiên phải khá rõ ràng, nếu bây giờ bạn bỏ qua thông tin trong ngoặc vuông. Điều cuối cùng có lẽ vẫn chưa rõ ràng, nhưng tôi sẽ giải thích chi tiết về điều đó ở phần sau của bài viết.

Thời gian SQL Server đợi một thao tác I / O xử lý quá trình xả bộ đệm nhật ký để hoàn tất được phản ánh bằng kiểu chờ WRITELOG.

Vậy, tại sao thông tin này lại thú vị đến vậy, và chúng ta sẽ làm gì với nó? Hiểu được các điều kiện kích hoạt xả bộ đệm nhật ký có thể giúp bạn tìm ra lý do tại sao một số khối lượng công việc nhất định gặp phải tình trạng tắc nghẽn liên quan. Ngoài ra, trong một số trường hợp, bạn có thể thực hiện các hành động để giảm bớt hoặc loại bỏ những tắc nghẽn như vậy. Tôi sẽ đề cập đến một số ví dụ như một giao dịch lớn so với nhiều giao dịch nhỏ, hoàn toàn lâu bền so với giao dịch lâu bền bị trì hoãn, cơ sở dữ liệu người dùng so với tempdb và bộ nhớ đệm đối tượng trình tự.

Một giao dịch lớn so với nhiều giao dịch nhỏ

Như đã đề cập, một trong những điều kiện kích hoạt xả bộ đệm nhật ký là khi bạn thực hiện một giao dịch để đảm bảo độ bền của giao dịch. Điều này có nghĩa là khối lượng công việc liên quan đến nhiều giao dịch nhỏ, như khối lượng công việc OLTP, có thể gặp phải tình trạng tắc nghẽn liên quan đến ghi nhật ký.

Mặc dù điều này thường không xảy ra, nhưng nếu bạn có một phiên duy nhất gửi nhiều thay đổi nhỏ, thì một cách đơn giản và hiệu quả để tối ưu hóa công việc là áp dụng các thay đổi trong một giao dịch lớn duy nhất thay vì nhiều giao dịch nhỏ.

Hãy xem xét ví dụ đơn giản sau (tải xuống PerformanceV3 tại đây):

SET NOCOUNT ON;
 
USE PerformanceV3;
 
ALTER DATABASE PerformanceV3 SET DELAYED_DURABILITY = Disabled; -- default
 
DROP TABLE IF EXISTS dbo.T1;
 
CREATE TABLE dbo.T1(col1 INT NOT NULL);
 
DECLARE @i AS INT = 1;
 
WHILE @i <= 1000000
BEGIN
 
  BEGIN TRAN
    INSERT INTO dbo.T1(col1) VALUES(@i);
  COMMIT TRAN;
 
  SET @i += 1;
END;

Mã này thực hiện 1.000.000 giao dịch nhỏ thay đổi dữ liệu trong cơ sở dữ liệu người dùng. Công việc này sẽ kích hoạt ít nhất 1.000.000 lần xả bộ đệm nhật ký. Bạn có thể nhận được một vài cái bổ sung do bộ đệm nhật ký đầy. Bạn có thể sử dụng mẫu thử nghiệm sau để đếm số lần xả bộ đệm nhật ký và đo thời gian hoàn thành công việc:

-- Test template
 
-- ... Preparation goes here ...
 
-- Count log flushes and measure time
DECLARE @logflushes AS INT, @starttime AS DATETIME2, @duration AS INT;
 
-- Stats before
SET @logflushes = ( SELECT cntr_value FROM sys.dm_os_performance_counters
                    WHERE counter_name = 'Log Flushes/sec'
                      AND instance_name = @db );
 
SET @starttime = SYSDATETIME();
 
-- ... Actual work goes here ...
 
-- Stats after
SET @duration = DATEDIFF(second, @starttime, SYSDATETIME());
SET @logflushes = ( SELECT cntr_value FROM sys.dm_os_performance_counters
                    WHERE counter_name = 'Log Flushes/sec'
                      AND instance_name = @db ) - @logflushes;
 
SELECT 
  @duration AS durationinseconds,
  @logflushes AS logflushes;

Mặc dù tên của bộ đếm hiệu suất là Log Flushes / sec, nhưng nó thực sự vẫn tiếp tục tích lũy số lượng log flush cho đến nay. Vì vậy, mã trừ số lượng trước khi làm việc với số lượng sau khi làm việc để tính ra số lần lưu nhật ký được tạo ra bởi công việc. Mã này cũng đo thời gian tính bằng giây mà công việc đã hoàn thành. Mặc dù tôi không làm điều này ở đây, nếu bạn muốn, tương tự như vậy, bạn có thể tìm ra số lượng bản ghi nhật ký và kích thước được ghi vào nhật ký của công việc bằng cách truy vấn trạng thái trước khi làm việc và sau khi làm việc của fn_dblog chức năng.

Đối với ví dụ của chúng tôi ở trên, sau đây là phần bạn cần đặt trong phần chuẩn bị của mẫu thử nghiệm:

-- Preparation
SET NOCOUNT ON;
USE PerformanceV3;
 
ALTER DATABASE PerformanceV3 SET DELAYED_DURABILITY = Disabled;
 
DROP TABLE IF EXISTS dbo.T1;
 
CREATE TABLE dbo.T1(col1 INT NOT NULL);
 
DECLARE @db AS sysname = N'PerformanceV3';
 
DECLARE @logflushes AS INT, @starttime AS DATETIME2, @duration AS INT;

Và sau đây là phần mà bạn cần đặt trong phần công việc thực tế:

-- Actual work
DECLARE @i AS INT = 1;
 
WHILE @i <= 1000000
BEGIN
 
  BEGIN TRAN
    INSERT INTO dbo.T1(col1) VALUES(@i);
  COMMIT TRAN;
 
  SET @i += 1;
END;

Nhìn chung, bạn nhận được mã sau:

-- Example test with many small fully durable transactions in user database
-- ... Preparation goes here ...
 
-- Preparation
SET NOCOUNT ON;
USE PerformanceV3;
 
ALTER DATABASE PerformanceV3 SET DELAYED_DURABILITY = Disabled;
 
DROP TABLE IF EXISTS dbo.T1;
 
CREATE TABLE dbo.T1(col1 INT NOT NULL);
 
DECLARE @db AS sysname = N'PerformanceV3';
 
DECLARE @logflushes AS INT, @starttime AS DATETIME2, @duration AS INT;
 
-- Stats before
SET @logflushes = ( SELECT cntr_value FROM sys.dm_os_performance_counters
 
                    WHERE counter_name = 'Log Flushes/sec'
 
                      AND instance_name = @db );
 
SET @starttime = SYSDATETIME();
 
-- ... Actual work goes here ...
 
-- Actual work
DECLARE @i AS INT = 1;
 
WHILE @i <= 1000000
BEGIN
 
  BEGIN TRAN
    INSERT INTO dbo.T1(col1) VALUES(@i);
  COMMIT TRAN;
 
  SET @i += 1;
END;
 
-- Stats after
SET @duration = DATEDIFF(second, @starttime, SYSDATETIME());
 
SET @logflushes = ( SELECT cntr_value FROM sys.dm_os_performance_counters
                    WHERE counter_name = 'Log Flushes/sec'
                      AND instance_name = @db ) - @logflushes;
 
SELECT 
  @duration AS durationinseconds,
  @logflushes AS logflushes;

Mã này mất 193 giây để hoàn thành trên hệ thống của tôi và kích hoạt 1.000.036 lần lưu bộ đệm nhật ký. Điều đó diễn ra rất chậm, nhưng có thể giải thích do số lượng lớn nhật ký xả.

Trong khối lượng công việc OLTP điển hình, các phiên khác nhau gửi đồng thời các thay đổi nhỏ trong các giao dịch nhỏ khác nhau, vì vậy, bạn không thực sự có tùy chọn để gói gọn nhiều thay đổi nhỏ trong một giao dịch lớn. Tuy nhiên, nếu tình huống của bạn là tất cả các thay đổi nhỏ được gửi từ cùng một phiên, thì một cách đơn giản để tối ưu hóa công việc là gói gọn nó trong một giao dịch duy nhất. Điều này sẽ mang lại cho bạn hai lợi ích chính. Một là công việc của bạn sẽ ghi ít bản ghi nhật ký hơn. Với 1.000.000 giao dịch nhỏ, mỗi giao dịch thực sự ghi ba bản ghi nhật ký:một bản ghi để bắt đầu giao dịch, một bản ghi thay đổi và một bản ghi để thực hiện giao dịch. Vì vậy, bạn đang xem xét khoảng 3.000.0000 bản ghi nhật ký giao dịch so với hơn 1.000.000 khi được thực hiện như một giao dịch lớn. Nhưng quan trọng hơn, với một giao dịch lớn, hầu hết các lần xóa nhật ký chỉ được kích hoạt khi bộ đệm nhật ký đầy, cộng thêm một lần xả nhật ký nữa vào cuối giao dịch khi nó thực hiện. Sự khác biệt về hiệu suất có thể khá đáng kể. Để kiểm tra công việc trong một giao dịch lớn, hãy sử dụng mã sau trong phần công việc thực tế của mẫu thử nghiệm:

-- Actual work
BEGIN TRAN;
 
DECLARE @i AS INT = 1;
 
WHILE @i <= 1000000
BEGIN
 
  INSERT INTO dbo.T1(col1) VALUES(@i);
  SET @i += 1;
 
END;
 
COMMIT TRAN;

Trên hệ thống của tôi, công việc này hoàn thành trong 7 giây và kích hoạt 1.758 lần lưu nhật ký. Dưới đây là so sánh giữa hai tùy chọn:

#transactions  log flushes  duration in seconds
-------------- ------------ --------------------
1000000        1000036      193
1              1758         7

Nhưng một lần nữa, trong khối lượng công việc OLTP thông thường, bạn không thực sự có tùy chọn để thay thế nhiều giao dịch nhỏ được gửi từ các phiên khác nhau bằng một giao dịch lớn được gửi từ cùng một phiên.

Hoàn toàn lâu bền so với các giao dịch lâu bền bị trì hoãn

Bắt đầu với SQL Server 2014, bạn có thể sử dụng một tính năng được gọi là độ bền trì hoãn cho phép bạn cải thiện hiệu suất của khối lượng công việc với nhiều giao dịch nhỏ, ngay cả khi được gửi bởi các phiên khác nhau, bằng cách hy sinh đảm bảo độ bền đầy đủ thông thường. Khi cam kết một giao dịch lâu bền bị trì hoãn, SQL Server xác nhận cam kết ngay khi bản ghi nhật ký cam kết được ghi vào bộ đệm nhật ký, mà không kích hoạt xả bộ đệm nhật ký. Bộ đệm nhật ký bị xóa do bất kỳ điều kiện nào khác đã nói ở trên như khi nó đầy, nhưng không phải khi một giao dịch lâu bền bị trì hoãn được thực hiện.

Trước khi sử dụng tính năng này, bạn cần phải suy nghĩ thật kỹ xem nó có phù hợp với mình không. Về mặt hiệu suất, tác động của nó chỉ đáng kể trong khối lượng công việc với nhiều giao dịch nhỏ. Nếu để bắt đầu với khối lượng công việc của bạn chủ yếu liên quan đến các giao dịch lớn, bạn có thể sẽ không thấy bất kỳ lợi thế nào về hiệu suất. Quan trọng hơn, bạn cần nhận ra khả năng mất dữ liệu. Giả sử ứng dụng cam kết một giao dịch lâu bền bị trì hoãn. Một bản ghi cam kết được ghi vào bộ đệm nhật ký và được thừa nhận ngay lập tức (quyền kiểm soát được trao lại cho người gọi). Nếu SQL Server gặp sự cố mất điện trước khi xóa bộ đệm nhật ký, sau khi khởi động lại, quá trình khôi phục sẽ hoàn tác tất cả các thay đổi được thực hiện bởi giao dịch, ngay cả khi ứng dụng cho rằng nó đã được cam kết.

Vì vậy, khi nào thì có thể sử dụng tính năng này? Một trường hợp rõ ràng là khi mất dữ liệu không phải là vấn đề, như ví dụ này từ Melissa Connors của SentryOne. Một cách khác là khi sau khi khởi động lại, bạn có phương tiện để xác định những thay đổi nào không xảy ra với cơ sở dữ liệu và bạn có thể tái tạo chúng. Nếu trường hợp của bạn không thuộc một trong hai loại này, đừng sử dụng tính năng này bất chấp sự cám dỗ.

Để xử lý các giao dịch lâu bền bị trì hoãn, bạn cần đặt tùy chọn cơ sở dữ liệu có tên DELAYED_DURABILITY. Tùy chọn này có thể được đặt thành một trong ba giá trị:

  • Đã tắt (mặc định):tất cả các giao dịch trong cơ sở dữ liệu hoàn toàn bền và do đó mỗi lần cam kết sẽ kích hoạt một đợt tuôn ra bộ đệm nhật ký
  • Cưỡng bức :tất cả các giao dịch trong cơ sở dữ liệu bị trì hoãn lâu bền, và do đó các cam kết không kích hoạt xả bộ đệm nhật ký
  • Được phép :trừ khi được đề cập khác, các giao dịch hoàn toàn bền vững và việc cam kết chúng sẽ kích hoạt việc tuôn ra bộ đệm nhật ký; tuy nhiên, nếu bạn sử dụng tùy chọn DELAYED_DURABILITY =ON trong câu lệnh COMMIT TRAN hoặc khối nguyên tử (của một proc được biên dịch nguyên bản), thì giao dịch cụ thể đó sẽ bị trì hoãn lâu bền và do đó việc cam kết sẽ không kích hoạt quá trình xả bộ đệm nhật ký

Để kiểm tra, hãy sử dụng mã sau trong phần chuẩn bị của mẫu thử nghiệm của chúng tôi (lưu ý rằng tùy chọn cơ sở dữ liệu được đặt thành Buộc):

-- Preparation
SET NOCOUNT ON;
USE PerformanceV3; -- http://tsql.solidq.com/SampleDatabases/PerformanceV3.zip
 
ALTER DATABASE PerformanceV3 SET DELAYED_DURABILITY = Forced;
 
DROP TABLE IF EXISTS dbo.T1;
 
CREATE TABLE dbo.T1(col1 INT NOT NULL);
 
DECLARE @db AS sysname = N'PerformanceV3';

Và sử dụng đoạn mã sau trong phần công việc thực tế (thông báo, 1.000.000 giao dịch nhỏ):

-- Actual work
DECLARE @i AS INT = 1;
 
WHILE @i <= 1000000
BEGIN
 
  BEGIN TRAN
    INSERT INTO dbo.T1(col1) VALUES(@i);
  COMMIT TRAN;
 
  SET @i += 1;
END;

Ngoài ra, bạn có thể sử dụng chế độ Được phép ở cấp cơ sở dữ liệu, sau đó trong lệnh COMMIT TRAN, hãy thêm WITH (DELAYED_DURABILITY =ON).

Trên hệ thống của tôi, công việc mất 22 giây để hoàn thành và kích hoạt 95.407 lần lưu nhật ký. Điều đó lâu hơn so với việc chạy công việc như một giao dịch lớn (7 giây) vì nhiều bản ghi nhật ký hơn được tạo (hãy nhớ, mỗi giao dịch, một bản ghi để bắt đầu giao dịch, một bản ghi thay đổi và một bản ghi để thực hiện giao dịch); tuy nhiên, nhanh hơn nhiều so với 193 giây mà công việc phải mất để hoàn thành bằng 1.000.000 giao dịch lâu bền hoàn toàn vì số lần xóa nhật ký giảm từ hơn 1.000.000 xuống dưới 100.000. Ngoài ra, với độ bền bị trì hoãn, bạn sẽ nhận được hiệu suất ngay cả khi các giao dịch được gửi từ các phiên khác nhau, nơi không phải là một tùy chọn để sử dụng một giao dịch lớn.

Để chứng minh rằng không có lợi ích gì khi sử dụng độ bền bị trì hoãn khi thực hiện công việc như các giao dịch lớn, hãy giữ nguyên mã trong phần chuẩn bị của thử nghiệm cuối cùng và sử dụng mã sau trong phần công việc thực tế:

-- Actual work
BEGIN TRAN;
 
DECLARE @i AS INT = 1;
 
WHILE @i <= 1000000
BEGIN
  INSERT INTO dbo.T1(col1) VALUES(@i);
 
  SET @i += 1;
END;
 
COMMIT TRAN;

Tôi có thời gian chạy 8 giây (so với 7 cho một giao dịch lớn hoàn toàn lâu bền) và 1.759 lần xả log (so với 1.758). Các con số về cơ bản giống nhau, nhưng với giao dịch lâu bền bị trì hoãn, bạn có nguy cơ mất dữ liệu.

Dưới đây là tóm tắt về các con số hiệu suất cho cả bốn bài kiểm tra:

durability          #transactions  log flushes  duration in seconds
------------------- -------------- ------------ --------------------
full                1000000        1000036      193
full                1              1758         7
delayed             1000000        95407        22
delayed             1              1759         8

Bộ nhớ lớp lưu trữ

Tính năng độ bền bị trễ có thể cải thiện đáng kể hiệu suất của khối lượng công việc kiểu OLTP liên quan đến một số lượng lớn các giao dịch cập nhật nhỏ yêu cầu tần suất cao và độ trễ thấp. Vấn đề là bạn đang có nguy cơ mất dữ liệu. Điều gì sẽ xảy ra nếu bạn không thể để xảy ra bất kỳ sự mất mát dữ liệu nào nhưng bạn vẫn muốn tăng hiệu suất giống như độ bền bị trì hoãn, trong đó bộ đệm nhật ký không bị xóa cho mỗi lần cam kết, thay vì đầy khi nó đầy? Tất cả chúng ta đều thích ăn bánh và ăn nó, phải không?

Bạn có thể đạt được điều này trong SQL Server 2016 SP1 trở lên bằng cách sử dụng bộ nhớ lớp lưu trữ, hay còn gọi là bộ nhớ không linh hoạt NVDIMM-N. Phần cứng này về cơ bản là một mô-đun bộ nhớ cung cấp cho bạn hiệu suất cấp bộ nhớ, nhưng thông tin ở đó vẫn tồn tại và do đó không bị mất khi mất điện. Việc bổ sung trong SQL Server 2016 SP1 cho phép bạn định cấu hình bộ đệm nhật ký là bộ đệm tồn tại lâu dài trên phần cứng đó. Để thực hiện việc này, bạn thiết lập SCM dưới dạng một ổ đĩa trong Windows và định dạng nó thành một ổ đĩa Chế độ Truy cập Trực tiếp (DAX). Sau đó, bạn thêm tệp nhật ký vào cơ sở dữ liệu bằng lệnh ALTER DATABASE ADD LOG FILE thông thường, với đường dẫn tệp nằm trên ổ đĩa DAX và đặt kích thước thành 20 MB. Đến lượt nó, SQL Server nhận ra rằng đó là một ổ đĩa DAX và từ thời điểm đó coi bộ đệm nhật ký là một bộ đệm tồn tại trên ổ đĩa đó. Các sự kiện cam kết giao dịch không kích hoạt xóa bộ đệm nhật ký nữa, thay vì sau khi cam kết được ghi lại trong bộ đệm nhật ký, SQL Server biết rằng nó thực sự vẫn tồn tại và do đó trả lại quyền kiểm soát cho người gọi. Khi bộ đệm nhật ký đầy, SQL Server sẽ chuyển nó vào tệp nhật ký giao dịch trên bộ nhớ truyền thống.

Để biết thêm chi tiết về tính năng này, bao gồm cả số hiệu suất, hãy xem Tăng tốc độ trễ cam kết giao dịch sử dụng Bộ nhớ lớp lưu trữ trong Windows Server 2016 / SQL Server 2016 SP1 của Kevin Farlee.

Thật kỳ lạ, SQL Server 2019 tăng cường hỗ trợ cho bộ nhớ lớp lưu trữ ngoài kịch bản bộ nhớ cache nhật ký liên tục. Nó hỗ trợ đặt các tệp dữ liệu, tệp nhật ký và tệp điểm kiểm tra OLTP trong Bộ nhớ trên phần cứng đó. Tất cả những gì bạn cần làm là hiển thị nó dưới dạng âm lượng ở cấp hệ điều hành và định dạng dưới dạng ổ DAX. SQL Server 2019 tự động nhận dạng công nghệ này và hoạt động trong một giác ngộ , truy cập trực tiếp vào thiết bị, bỏ qua ngăn xếp bộ nhớ của Hệ điều hành. Chào mừng bạn đến với tương lai!

Cơ sở dữ liệu người dùng so với tempdb

Cơ sở dữ liệu tempdb tất nhiên được tạo từ đầu như một bản sao mới của cơ sở dữ liệu mô hình mỗi khi bạn khởi động lại SQL Server. Do đó, không bao giờ cần khôi phục bất kỳ dữ liệu nào bạn ghi vào tempdb, cho dù bạn ghi dữ liệu đó vào bảng tạm thời, biến bảng hay bảng người dùng. Tất cả đều biến mất sau khi khởi động lại. Biết được điều này, SQL Server có thể giảm bớt rất nhiều yêu cầu liên quan đến ghi nhật ký. Ví dụ:bất kể bạn có bật tùy chọn độ bền bị trì hoãn hay không, các sự kiện cam kết không kích hoạt xả bộ đệm nhật ký. Hơn nữa, lượng thông tin cần được ghi lại được giảm xuống vì SQL Server chỉ cần đủ thông tin để hỗ trợ các giao dịch quay lại hoặc hoàn tác công việc, nếu cần, nhưng không chuyển giao dịch về phía trước hoặc làm lại công việc. Do đó, các bản ghi nhật ký giao dịch đại diện cho các thay đổi đối với một đối tượng trong tempdb có xu hướng nhỏ hơn so với khi cùng một thay đổi được áp dụng cho một đối tượng trong cơ sở dữ liệu người dùng.

Để chứng minh điều này, bạn sẽ chạy các thử nghiệm giống như bạn đã chạy trước đó trong PerformanceV3, chỉ lần này là trong tempdb. Chúng tôi sẽ bắt đầu với việc kiểm tra nhiều giao dịch nhỏ khi tùy chọn cơ sở dữ liệu DELAYED_DURABILITY được đặt thành Tắt (mặc định). Sử dụng mã sau trong phần chuẩn bị của mẫu thử nghiệm:

-- Preparation
SET NOCOUNT ON;
USE tempdb;
 
ALTER DATABASE tempdb SET DELAYED_DURABILITY = Disabled;
 
DROP TABLE IF EXISTS dbo.T1;
 
CREATE TABLE dbo.T1(col1 INT NOT NULL);
 
DECLARE @db AS sysname = N'tempdb';

Sử dụng mã sau trong phần công việc thực tế:

-- Actual work
DECLARE @i AS INT = 1;
 
WHILE @i <= 1000000
BEGIN
 
  BEGIN TRAN
    INSERT INTO dbo.T1(col1) VALUES(@i);
  COMMIT TRAN;
 
  SET @i += 1;
END;

Công việc này tạo ra 5.095 lần lưu nhật ký và mất 19 giây để hoàn thành. Con số đó so với hơn một triệu lần xả nhật ký và 193 giây trong cơ sở dữ liệu người dùng với độ bền đầy đủ. Điều đó thậm chí còn tốt hơn so với độ bền bị trì hoãn trong cơ sở dữ liệu người dùng (95.407 lần lưu nhật ký và 22 giây) do kích thước của các bản ghi nhật ký giảm xuống.

Để kiểm tra một giao dịch lớn, hãy giữ nguyên phần chuẩn bị và sử dụng mã sau trong phần công việc thực tế:

-- Actual work
BEGIN TRAN;
 
DECLARE @i AS INT = 1;
 
WHILE @i <= 1000000
BEGIN
  INSERT INTO dbo.T1(col1) VALUES(@i);
 
  SET @i += 1;
END;
 
COMMIT TRAN;

Tôi nhận được 1.228 lần xả nhật ký và thời gian chạy là 9 giây. Con số đó so với 1.758 lần lưu nhật ký và thời gian chạy 7 giây trong cơ sở dữ liệu người dùng. Thời gian chạy tương tự, thậm chí nhanh hơn một chút trong cơ sở dữ liệu người dùng, nhưng nó có thể là những thay đổi nhỏ giữa các lần kiểm tra. Kích thước của các bản ghi nhật ký trong tempdb được giảm xuống và do đó, bạn nhận được ít lần lưu nhật ký hơn so với cơ sở dữ liệu người dùng.

Bạn cũng có thể thử chạy các bài kiểm tra với tùy chọn DELAYED_DURABILITY được đặt thành Buộc, nhưng điều này sẽ không ảnh hưởng đến tempdb vì như đã đề cập, các sự kiện cam kết dù sao cũng không kích hoạt lưu nhật ký trong tempdb.

Dưới đây là các thước đo hiệu suất cho tất cả các bài kiểm tra, cả trong cơ sở dữ liệu người dùng và trong tempdb:

database       durability          #transactions  log flushes  duration in seconds
-------------- ------------------- -------------- ------------ --------------------
PerformanceV3  full                1000000        1000036      193
PerformanceV3  full                1              1758         7
PerformanceV3  delayed             1000000        95407        22
PerformanceV3  delayed             1              1759         8
tempdb         full                1000000        5095         19
tempdb         full                1              1228         9
tempdb         delayed             1000000        5091         18
tempdb         delayed             1              1226         9

Bộ nhớ đệm đối tượng trình tự

Có lẽ một trường hợp đáng ngạc nhiên mà kích hoạt bộ đệm nhật ký liên quan đến tùy chọn bộ đệm đối tượng trình tự. Hãy coi như một ví dụ về định nghĩa trình tự sau:

CREATE SEQUENCE dbo.Seq1 AS BIGINT MINVALUE 1 CACHE 50; -- the default cache size is 50;

Mỗi khi bạn cần một giá trị trình tự mới, bạn sử dụng hàm NEXT VALUE FOR, như sau:

SELECT NEXT VALUE FOR dbo.Seq1;

Thuộc tính CACHE là một tính năng hiệu suất. Nếu không có nó, mỗi khi một giá trị trình tự mới được yêu cầu, SQL Server sẽ phải ghi giá trị hiện tại vào đĩa cho mục đích khôi phục. Thật vậy, đó là hành vi mà bạn nhận được khi sử dụng chế độ KHÔNG CACHE. Thay vào đó, khi tùy chọn được đặt thành giá trị lớn hơn 0, SQL Server chỉ ghi giá trị khôi phục vào đĩa một lần cho mỗi số lượng yêu cầu có kích thước bộ đệm. SQL Server duy trì hai thành viên trong bộ nhớ, có kích thước là kiểu trình tự, một chứa giá trị hiện tại và một chứa số giá trị còn lại trước khi đĩa tiếp theo ghi giá trị khôi phục. Trong trường hợp mất điện, khi khởi động lại, SQL Server đặt giá trị trình tự hiện tại thành giá trị khôi phục.

Điều này có lẽ dễ giải thích hơn nhiều với một ví dụ. Hãy xem xét định nghĩa trình tự ở trên với tùy chọn CACHE được đặt thành 50 (mặc định). Bạn yêu cầu một giá trị trình tự mới lần đầu tiên bằng cách chạy câu lệnh SELECT ở trên. SQL Server đặt các thành viên nói trên thành các giá trị sau:

On disk recovery value: 50, In-memory current value: 1, In-memory values left: 49, You get: 1

49 yêu cầu khác sẽ không chạm vào đĩa, thay vào đó chỉ cập nhật các thành viên bộ nhớ. Sau tổng số 50 yêu cầu, các thành viên được đặt thành các giá trị sau:

On disk recovery value: 50, In-memory current value: 50, In-memory values left: 0, You get: 50

Thực hiện một yêu cầu khác cho giá trị trình tự mới và điều này kích hoạt ghi vào đĩa giá trị khôi phục 100. Sau đó, các thành viên được đặt thành các giá trị sau:

On disk recovery value: 100, In-memory current value: 51, In-memory values left: 49, You get: 51

Nếu tại thời điểm này, hệ thống gặp sự cố mất điện, sau khi khởi động lại, giá trị trình tự hiện tại được đặt thành 100 (giá trị được khôi phục từ đĩa). Yêu cầu tiếp theo cho giá trị trình tự tạo ra 101 (ghi giá trị khôi phục 150 vào đĩa). Bạn đã mất tất cả các giá trị trong phạm vi từ 52 đến 100. Phần lớn bạn có thể bị mất do quá trình SQL Server bị chấm dứt không sạch, như trong trường hợp mất điện, là nhiều giá trị bằng kích thước bộ nhớ cache. Sự cân bằng là rõ ràng; kích thước bộ nhớ cache càng lớn, đĩa ghi càng ít giá trị khôi phục và do đó hiệu suất càng tốt. Đồng thời, khoảng cách có thể được tạo ra giữa hai giá trị thứ tự trong trường hợp mất điện càng lớn.

Tất cả điều này khá đơn giản và có lẽ bạn đã rất quen thuộc với cách hoạt động của nó. Điều có thể gây ngạc nhiên là mỗi khi SQL Server ghi một giá trị khôi phục mới vào đĩa (cứ 50 yêu cầu trong ví dụ của chúng tôi), nó cũng làm cứng bộ đệm nhật ký. Đó không phải là trường hợp của thuộc tính cột nhận dạng, mặc dù SQL Server nội bộ sử dụng cùng một tính năng bộ nhớ đệm cho danh tính giống như đối với đối tượng trình tự, nó không cho phép bạn kiểm soát kích thước của nó. Nó được bật theo mặc định với kích thước 10000 cho BIGINT và NUMERIC, 1000 cho INT, 100 cho SMALLINT và 10 cho TINYINT. Nếu muốn, bạn có thể tắt nó bằng cờ theo dõi 272 hoặc tùy chọn cấu hình phạm vi IDENTITY_CACHE (2017+). Lý do SQL Server không cần xóa bộ đệm nhật ký khi ghi giá trị khôi phục liên quan đến bộ đệm ẩn danh tính vào đĩa là giá trị nhận dạng mới chỉ có thể được tạo khi chèn một hàng vào bảng. Trong trường hợp mất điện, một hàng được chèn vào bảng bởi một giao dịch không được cam kết sẽ bị kéo ra khỏi bảng như một phần của quá trình khôi phục cơ sở dữ liệu khi hệ thống khởi động lại. Vì vậy, ngay cả khi sau khi khởi động lại Máy chủ SQL tạo ra cùng một giá trị nhận dạng như giá trị được tạo trong giao dịch không cam kết, sẽ không có cơ hội cho các bản sao vì hàng đã được kéo ra khỏi bảng. Nếu giao dịch được cam kết, điều này sẽ kích hoạt quá trình xóa nhật ký, điều này cũng sẽ duy trì việc ghi giá trị khôi phục liên quan đến bộ nhớ cache. Do đó, Microsoft không cảm thấy bắt buộc phải xóa bộ đệm nhật ký mỗi khi quá trình ghi giá trị khôi phục trên đĩa liên quan đến bộ nhớ cache-nhận dạng.

Với đối tượng trình tự, tình hình là khác nhau. Một ứng dụng có thể yêu cầu một giá trị trình tự mới và không lưu trữ nó trong cơ sở dữ liệu. Trong trường hợp mất điện sau khi tạo giá trị trình tự mới trong giao dịch không cam kết, sau khi khởi động lại, SQL Server không có cách nào để yêu cầu ứng dụng không dựa vào giá trị đó. Do đó, để tránh tạo giá trị trình tự mới sau khi khởi động lại bằng với giá trị trình tự đã tạo trước đó, SQL Server buộc xóa nhật ký mỗi khi giá trị khôi phục liên quan đến bộ đệm ẩn mới được ghi vào đĩa. Một ngoại lệ đối với quy tắc này là khi đối tượng trình tự được tạo trong tempdb, tất nhiên không cần nhật ký như vậy bị xóa vì dù sao sau khi khởi động lại hệ thống, tempdb đã được tạo lại.

Tác động tiêu cực đến hiệu suất của việc nhật ký thường xuyên tuôn ra đặc biệt đáng chú ý khi sử dụng kích thước bộ nhớ cache trình tự rất nhỏ và trong một giao dịch tạo ra nhiều giá trị trình tự, ví dụ:khi chèn nhiều hàng vào bảng. Nếu không có trình tự, một giao dịch như vậy hầu hết sẽ làm cứng bộ đệm nhật ký khi nó đầy, cộng thêm một lần nữa khi giao dịch được thực hiện. Nhưng với trình tự, bạn sẽ nhận được nhật ký tuôn ra mỗi khi đĩa ghi giá trị khôi phục diễn ra. Đó là lý do tại sao bạn muốn tránh sử dụng kích thước bộ nhớ cache nhỏ - chứ không phải nói về chế độ KHÔNG CACHE.

Để chứng minh điều này, hãy sử dụng mã sau trong phần chuẩn bị của mẫu thử nghiệm của chúng tôi:

-- Preparation
SET NOCOUNT ON;
USE PerformanceV3; -- try PerformanceV3, tempdb
 
ALTER DATABASE PerformanceV3         -- try PerformanceV3, tempdb
  SET DELAYED_DURABILITY = Disabled; -- try Disabled, Forced
 
DROP TABLE IF EXISTS dbo.T1;
 
DROP SEQUENCE IF EXISTS dbo.Seq1;
 
CREATE SEQUENCE dbo.Seq1 AS BIGINT MINVALUE 1 CACHE 50; -- try NO CACHE, CACHE 50, CACHE 10000
 
DECLARE @db AS sysname = N'PerformanceV3'; -- try PerformanceV3, tempdb

Và đoạn mã sau trong phần công việc thực tế:

-- Actual work
SELECT
  -- n -- to test without seq
  NEXT VALUE FOR dbo.Seq1 AS n -- to test sequence
INTO dbo.T1
FROM PerformanceV3.dbo.GetNums(1, 1000000) AS N;

Mã này sử dụng một giao dịch để ghi 1.000.000 hàng vào bảng bằng câu lệnh SELECT INTO, tạo ra nhiều giá trị trình tự bằng số hàng được chèn.

Như đã hướng dẫn trong phần nhận xét, hãy chạy thử nghiệm với KHÔNG CACHE, CACHE 50 và CACHE 10000, cả trong PerformanceV3 và tempdb, đồng thời thử cả các giao dịch lâu bền hoàn toàn và các giao dịch lâu bền.

Đây là số hiệu suất mà tôi nhận được trên hệ thống của mình:

database       durability          cache     log flushes  duration in seconds
-------------- ------------------- --------- ------------ --------------------
PerformanceV3  full                NO CACHE  1000047      171
PerformanceV3  full                50        20008        4
PerformanceV3  full                10000     339          < 1
tempdb         full                NO CACHE  96           4
tempdb         full                50        74           1
tempdb         full                10000     8            < 1
PerformanceV3  delayed             NO CACHE  1000045      166
PerformanceV3  delayed             50        20011        4
PerformanceV3  delayed             10000     334          < 1
tempdb         delayed             NO CACHE  91           4
tempdb         delayed             50        74           1
tempdb         delayed             10000     8            < 1

Có một số điều thú vị cần lưu ý.

Với KHÔNG CÓ CACHE, bạn nhận được một bản ghi nhật ký cho mỗi giá trị trình tự đơn được tạo ra. Do đó, chúng tôi đặc biệt khuyên bạn nên tránh nó.

Với kích thước bộ nhớ cache tuần tự nhỏ, bạn vẫn nhận được nhiều lần đăng nhập. Có lẽ tình hình không tồi tệ như với NO CACHE, nhưng hãy quan sát rằng khối lượng công việc mất 4 giây để hoàn thành với kích thước bộ nhớ cache mặc định là 50 so với chưa đầy một giây với kích thước 10.000. Cá nhân tôi sử dụng 10.000 làm giá trị ưa thích của mình.

In tempdb you don’t get log flushes when a sequence cache-related recovery value is written to disk, but the recovery value is still written to disk every cache-sized number of requests. That’s perhaps surprising since such a value would never need to be recovered. Therefore, even when using a sequence object in tempdb, I’d still recommend using a large cache size.

Also notice that delayed durability doesn’t prevent the need for log flushes every time the sequence cache-related recovery value is written to disk.

Kết luận

This article focused on log buffer flushes. Understanding this aspect of SQL Server’s logging architecture is important especially in order to be able to optimize OLTP-style workloads that require high frequency and low latency. Workloads using In-Memory OLTP included, of course. You have more options with newer features like delayed durability and persisted log buffer with storage class memory. Make sure you’re very careful with the former, though, since it does incur potential for data loss unlike the latter.

Be careful not to use the sequence object with a small cache size, not to speak of the NO CACHE mode. I find the default size 50 too small and prefer to use 10,000. I’ve heard people expressing concerns that with a cache size 10000, after multiple power failures they might lose all the values in the type. However, even with a four-byte INT type, using only the positive range, 10,000 fits 214,748 times. If your system experience that many power failures, you have a completely different problem to worry about. Therefore, I feel very comfortable with a cache size of 10,000.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Mức lương trung bình của một nhà phát triển SQL là gì?

  2. Cách sao chép dữ liệu từ bảng này sang bảng khác trong SQL

  3. Các nguyên tắc cơ bản về biểu thức bảng, Phần 10 - Các thay đổi về chế độ xem, SELECT * và DDL

  4. Mô hình Mối quan hệ Đảng. Làm thế nào để mô hình hóa các mối quan hệ

  5. Các nguyên tắc cơ bản về biểu thức bảng, Phần 8 - CTE, tiếp tục xem xét tối ưu hóa