Cơ sở dữ liệuSQL Server, phần 2/6

Kỹ thuật thường dùng

Nén, columnstore, SWITCH, Query Store, temporal table, TDE và sẵn sàng cao, gắn với database BanHang.

Mục lục
  1. 1. Chọn theo bài toán
  2. 2. Nén dòng và nén trang
  3. 3. Columnstore
  4. 4. Đẩy cả một năm ra bằng SWITCH
  5. 5. Fillfactor và rebuild
  6. 6. Đọc không chặn ghi
  7. 7. Accelerated Database Recovery
  8. 8. Commit chưa chờ log
  9. 9. Query Store
  10. 10. Chỉ mục khớp câu hỏi
  11. 11. Lịch sử dòng và hàng đợi đồng bộ
  12. 12. Che dữ liệu và khóa theo chi nhánh
  13. 13. Mã hóa file trên đĩa
  14. 14. Sẵn sàng cao
  15. 15. Việc chạy hằng ngày
  16. 16. Những thói quen nên bỏ
  17. 17. Thứ tự nên bật cho BanHang
  18. Đọc tiếp
  19. Nguồn

Chương này là bản đồ các kỹ thuật hay được dùng trên một database giao dịch như BanHang. Mỗi mục trả lời bốn câu: bài toán gì, làm bằng gì, ví dụ nào, và điều kiện nào. Cách page, filegroup, log và chuỗi backup vận hành nằm ở Kiến trúc lưu trữ.

Mốc phiên bản trong các ví dụ là SQL Server 2019 trở đi. Chỗ một tính năng chỉ có từ SQL Server 2022, hoặc chỉ có trên Enterprise, được ghi ngay tại mục đó. Từ SQL Server 2016 SP1, Standard đã có nén dữ liệu, partition, columnstore, CDC, row-level security và dynamic data masking.

Đọc nhanh

  • Bắt đầu từ bảng "Chọn theo bài toán" ở mục 1. Mỗi dòng trỏ tới một mục có lệnh chạy được.
  • Bật Query Store trước khi đổi chỉ mục hoặc compatibility level, để còn so được trước và sau.
  • Xóa cả một năm dữ liệu bằng SWITCH partition rồi TRUNCATE, không bằng DELETE.
  • READ_COMMITTED_SNAPSHOT thay cho NOLOCK khi đọc và ghi chặn nhau.
  • TDE chỉ an toàn khi certificate đã được sao lưu ra máy khác. Availability Group không thay backup.

1. Chọn theo bài toán

Bài toán trên BanHang Kỹ thuật nên xét trước
File dữ liệu và backup quá lớn, CPU còn dư Nén trang trên partition năm hiện tại
Báo cáo SUM/GROUP BY trên nhiều năm đơn Columnstore, để riêng khỏi tìm một đơn theo khóa
Xóa hoặc lưu trữ cả một năm mà log không phình SWITCH partition rồi TRUNCATE
Đọc đơn bị đứng vì người khác đang sửa READ_COMMITTED_SNAPSHOT
Rollback và recovery sau giao dịch dài quá lâu Accelerated Database Recovery, từ SQL Server 2019
Một câu SQL hôm qua nhanh, hôm nay chậm Query Store
Cần biết hôm qua TongTien của đơn 10042 là bao nhiêu Bảng temporal
Ổ đĩa hoặc file backup ra khỏi tủ TDE, cộng sao lưu certificate
Nhân viên chi nhánh chỉ thấy đơn của mình Row-level security
Màn hình tổng đài không hiện đủ số điện thoại Dynamic data masking
Mất một máy mà chỉ được mất vài phút dữ liệu Log shipping, Basic Availability Group, hoặc Availability Group
Biết backup có lành không DBCC CHECKDB và restore thử

2. Nén dòng và nén trang

Nén làm nhiều dòng hơn nằm trong một page 8 KB, nên cùng một lần đọc đĩa trả về nhiều đơn hơn. Đổi lại, mỗi lần ghi tốn thêm CPU để nén và giải nén.

Mức Việc làm
ROW Ghi số, ngày và chuỗi cố định theo cách ngắn hơn khi giá trị không dùng hết độ rộng đã khai báo
PAGE Làm mọi thứ của ROW, rồi thêm nén tiền tố và từ điển trên cả page

Ước lượng partition năm 2026 trước khi đụng dữ liệu thật. index_id = 1 là clustered index.

USE BanHang;
GO

EXEC sys.sp_estimate_data_compression_savings
    @schema_name = N'dbo',
    @object_name = N'DonHang',
    @index_id = 1,
    @partition_number = 3,
    @data_compression = N'PAGE';

Cột size_with_requested_compression_setting(KB) nhỏ hơn rõ so với kích thước hiện tại, và CPU máy còn trống lúc cao điểm, thì nén partition đó:

ALTER INDEX PK_DonHang
    ON dbo.DonHang
    REBUILD PARTITION = 3
    WITH (DATA_COMPRESSION = PAGE, MAXDOP = 4);

REBUILD viết lại partition và giữ khóa đến khi xong trên bản Standard. Bản Enterprise thêm ONLINE = ON để đơn mới vẫn được ghi trong lúc rebuild. Partition lưu trữ trên FG_ARCHIVE thường lợi hơn partition đang nhận đơn từng giây, vì trang nén được giải nén rồi nén lại mỗi lần UPDATE.

varchar(max), varbinary(max) và file đã nén sẵn (PDF, JPEG, ZIP) gần như không nhỏ thêm. Nén page không thay chuỗi backup: backup vẫn nên bật COMPRESSION như chương lưu trữ đã làm.

3. Columnstore

Chỉ mục rowstore (cây B-tree của PK_DonHang) giỏi tìm một đơn theo khóa. Columnstore giỏi đọc vài cột trên rất nhiều dòng, vì mỗi cột nằm thành đoạn riêng và được nén theo kiểu của cột đó.

Một chỉ mục columnstore không clustered trên chính dbo.DonHang phục vụ báo cáo mà không bỏ cây khóa hiện tại:

CREATE NONCLUSTERED COLUMNSTORE INDEX NCCI_DonHang
    ON dbo.DonHang (NgayTao, KhachHangId, TrangThai, TongTien)
    ON ps_DonHang_Ngay (NgayTao);

Câu sau có thể chạy trên columnstore:

SELECT
    CAST(NgayTao AS date) AS ngay,
    SUM(TongTien) AS doanh_thu
FROM dbo.DonHang
WHERE NgayTao >= CAST('20260101' AS datetime2(0))
  AND NgayTao < CAST('20270101' AS datetime2(0))
GROUP BY CAST(NgayTao AS date);

Dòng vừa INSERT hoặc UPDATE nằm ở một rowgroup đang mở (deltastore) cho đến khi đủ khoảng một triệu dòng hoặc một tiến trình nền nén thành rowgroup cột. Báo cáo sát giờ vừa ghi vẫn đúng, nhưng phần mới chưa được nén đặc như phần cũ.

Tìm một đơn WHERE DonHangId = 10042 tiếp tục dùng clustered index. Ép mọi câu sang columnstore làm điểm tra cứu đơn lẻ chậm đi. Bảng chỉ nhận thêm và chủ yếu để quét, như bảng lưu trữ đã khóa, hợp với clustered columnstore hơn là nonclustered.

Columnstore có từ SQL Server 2016 SP1 trên Standard. Mọi chỉ mục của bảng, kể cả columnstore, phải có mặt trên bảng staging thì SWITCH ở mục sau mới chạy.

4. Đẩy cả một năm ra bằng SWITCH

DELETE đơn trước năm 2025 ghi log cho từng dòng. Với hàng chục triệu đơn, log trên L: phình và lệnh chạy rất lâu. SWITCH chỉ đổi metadata: partition 1 (mọi NgayTao trước 2025-01-01) được trao cho một bảng trống trên cùng filegroup FG_ARCHIVE. TRUNCATE bảng đó xong là dữ liệu biến mất khỏi database đang online.

FG_ARCHIVE phải đang READ_WRITE. Filegroup read-only không nhận SWITCH. Script dưới đây giả định bảng chỉ có PK_DonHang và IX_DonHang_KhachHang như chương lưu trữ. Mỗi chỉ mục thêm vào dbo.DonHang sau này phải có bản giống hệt trên bảng staging, cùng cột khóa, cùng chiều sắp xếp, cùng cột INCLUDE, nếu không SWITCH thất bại với lỗi 4947:

  • NCCI_DonHang: một columnstore cùng danh sách cột, cùng mức nén với partition nguồn.
  • IX_DonHang_DangMo ở mục 10: cùng điều kiện lọc WHERE TrangThai = 1.
  • IX_DonHang_DonHangId của Kiểu dữ liệu, collation và khóa chính.
  • IX_DonHang_KhachHang sau khi thêm INCLUDE (TrangThai, TongTien) ở Điều tra truy vấn chậm: chỉ mục staging cũng phải INCLUDE đúng hai cột đó.
CREATE TABLE dbo.DonHang_Staging (
    DonHangId bigint NOT NULL,
    NgayTao datetime2(0) NOT NULL,
    KhachHangId int NOT NULL,
    TrangThai tinyint NOT NULL,
    TongTien decimal(18, 2) NOT NULL,
    CONSTRAINT PK_DonHang_Staging PRIMARY KEY CLUSTERED (NgayTao, DonHangId),
    CONSTRAINT CK_DonHang_Staging_2024 CHECK (NgayTao < CAST('20250101' AS datetime2(0)))
) ON FG_ARCHIVE;
GO

CREATE INDEX IX_DonHang_Staging_KhachHang
    ON dbo.DonHang_Staging (KhachHangId, NgayTao)
    ON FG_ARCHIVE;
GO

ALTER TABLE dbo.DonHang SWITCH PARTITION 1 TO dbo.DonHang_Staging;
GO

TRUNCATE TABLE dbo.DonHang_Staging;

Bảng staging phải cùng cột, cùng thứ tự, cùng clustered key với dbo.DonHang, nằm trên đúng filegroup của partition nguồn, và đang trống. Ràng buộc CHECK phải phủ đúng miền của partition đó và được tin cậy. Khóa ngoại từ bảng khác trỏ vào dbo.DonHang chặn SWITCH cho đến khi được xử lý. Sau TRUNCATE, có thể DROP TABLE dbo.DonHang_Staging.

Cùng kỹ thuật này dùng để đưa năm cũ sang bảng lưu trữ: SWITCH ra staging, rồi INSERT sang bảng columnstore trên F: trong cửa sổ bảo trì, thay vì DELETE trên bảng nóng.

5. Fillfactor và rebuild

Fillfactor chừa chỗ trống trên page lá lúc tạo hoặc rebuild chỉ mục, để lần INSERT sau đỡ tách page. Khóa clustered (NgayTao, DonHangId) của đơn mới gần như chỉ ghi vào mép phải của cây. Chừa 20% trống trên khóa này làm page năm 2026 thưa mà không giảm tách page. Giữ fillfactor 100 cho khóa đó.

Chỉ mục trên cột bị sửa hoặc chèn vào giữa, nếu đo được tách page dày, mới hạ fillfactor xuống khoảng 90 lúc rebuild. Trên SSD, page vỡ vụn hại ít hơn trên HDD. Cái đáng đo là mật độ page và thống kê lỗi thời, không phải một ngưỡng phân mảnh cố định áp cho mọi chỉ mục mỗi đêm.

REBUILD viết lại chỉ mục và cập nhật thống kê của chỉ mục đó. Chỉ mục không phân vùng được tính thống kê trên toàn bộ dòng. Chỉ mục đã phân vùng như PK_DonHang thì, từ SQL Server 2014, thống kê sau rebuild dùng mẫu mặc định chứ không quét toàn bộ. REORGANIZE dồn page lá, chạy online trên mọi edition, và không cập nhật thống kê. Chỉ mục dưới khoảng 1.000 page không đáng lịch rebuild.

6. Đọc không chặn ghi

Mức READ COMMITTED mặc định lấy khóa chung khi đọc. Một UPDATE đơn 10042 đang mở transaction thì câu SELECT đơn đó đứng chờ. READ_COMMITTED_SNAPSHOT giữ nguyên mức cô lập của ứng dụng, nhưng bản đọc lấy phiên bản dòng đã commit gần nhất.

ALTER DATABASE BanHang SET READ_COMMITTED_SNAPSHOT ON;

Lệnh chờ đến khi các phiên khác rời BanHang. Thêm WITH ROLLBACK IMMEDIATE nếu chấp nhận ngắt các phiên đó trong cửa sổ bảo trì.

Phiên bản dòng nằm ở version store của tempdb. tempdb cần chỗ và nên nằm trên đĩa nhanh. Khi Accelerated Database Recovery bật, phiên bản chuyển sang persisted version store trong chính BanHang.

Xem version store của tempdb:

USE tempdb;
GO

SELECT
    SUM(version_store_reserved_page_count) * 8 / 1024.0 AS version_store_mb
FROM sys.dm_db_file_space_usage;

WITH (NOLOCK) bỏ khóa chung và có thể đọc dòng đang bị sửa dở, đọc đôi hoặc bỏ sót dòng khi page tách. Khi cần báo cáo không chờ ghi, bật READ_COMMITTED_SNAPSHOT cho database thay vì gắn NOLOCK lên từng câu.

7. Accelerated Database Recovery

Từ SQL Server 2019, ADR đổi cách undo và recovery. Giao dịch dài không còn kéo thời gian rollback và thời gian mở lại database theo số dòng nó đã sửa. Log cũng được cắt sớm hơn dù còn transaction chưa commit. Có trên Standard, Enterprise và Web. Express không có.

Persisted version store (PVS) mặc định nằm trên filegroup PRIMARY. Với BanHang, đó là tệp 512 MB trên D: của chương lưu trữ, không phải FG_DATA. Chỉ định filegroup ngay lúc bật, để phiên bản dòng nằm trên ổ nhanh và có chỗ lớn:

ALTER DATABASE BanHang SET ACCELERATED_DATABASE_RECOVERY = ON
    (PERSISTENT_VERSION_STORE_FILEGROUP = FG_DATA);

Lệnh cần khóa độc quyền trên database: nó chờ đến khi mọi phiên khác rời BanHang, và phiên mới xếp hàng sau nó. Chạy ngoài giờ cao điểm, thêm WITH ROLLBACK IMMEDIATE nếu chấp nhận ngắt các phiên đang mở. Đã bật ADR mà PVS đang ở PRIMARY thì muốn chuyển phải tắt ADR, chờ PVS dọn về 0, rồi bật lại kèm filegroup mới. Theo dõi:

SELECT persistent_version_store_size_kb / 1024.0 AS pvs_mb
FROM sys.dm_tran_persistent_version_store_stats
WHERE database_id = DB_ID(N'BanHang');

ADR không thay chuỗi full, differential và log backup. Điểm phục hồi theo thời điểm vẫn do log backup quyết định.

8. Commit chưa chờ log

Delayed durability cho COMMIT trả về trước khi log record xuống L:. Chương lưu trữ mô tả commit thường chờ log flush. Bỏ bước chờ đó tăng thông lượng khi đĩa log là nút cổ chai, và đánh đổi bằng việc mất các giao dịch đã báo thành công nếu mất điện trước lần flush kế tiếp.

ALTER DATABASE BanHang SET DELAYED_DURABILITY = ALLOWED;
GO

BEGIN TRAN;
    UPDATE dbo.DonHang
    SET TrangThai = 2
    WHERE DonHangId = 10042
      AND NgayTao = CAST('2026-10-02T11:58:00' AS datetime2(0));
COMMIT TRAN WITH (DELAYED_DURABILITY = ON);

Dùng cho luồng ghi dày mà mất vài giao dịch cuối vẫn chấp nhận được, ví dụ nhật ký truy cập. Đơn đã thu tiền giữ commit bền, tức không gắn cờ này.

9. Query Store

Query Store giữ câu SQL, kế hoạch thực thi và số liệu chạy (thời gian, CPU, số lần đọc). Từ SQL Server 2022, database tạo mới bật Query Store theo mặc định. Database nâng cấp từ bản cũ thường vẫn tắt cho đến khi bật tay.

ALTER DATABASE BanHang SET QUERY_STORE = ON;
ALTER DATABASE BanHang SET QUERY_STORE (
    OPERATION_MODE = READ_WRITE,
    QUERY_CAPTURE_MODE = AUTO,
    MAX_STORAGE_SIZE_MB = 1024
);

Mười câu tốn nhiều thời gian nhất trong store hiện tại:

SELECT TOP (10)
    qsq.query_id,
    LEFT(qt.query_sql_text, 200) AS query_text,
    SUM(rs.count_executions) AS executions,
    SUM(rs.avg_duration * rs.count_executions) / 1000.0 AS total_duration_ms
FROM sys.query_store_query_text AS qt
JOIN sys.query_store_query AS qsq
    ON qt.query_text_id = qsq.query_text_id
JOIN sys.query_store_plan AS qsp
    ON qsq.query_id = qsp.query_id
JOIN sys.query_store_runtime_stats AS rs
    ON qsp.plan_id = rs.plan_id
GROUP BY qsq.query_id, qt.query_sql_text
ORDER BY total_duration_ms DESC;

avg_duration tính bằng micro giây. Nhân với số lần chạy rồi chia 1.000 ra mili giây.

Một thủ tục lọc theo @TrangThai có thể biên dịch kế hoạch tốt cho trạng thái hiếm, rồi dùng lại kế hoạch đó cho trạng thái phổ biến. Query Store cho thấy hai kế hoạch của cùng một câu và thời điểm kế hoạch đổi. Cách xử lý thường gặp, theo thứ tự nên thử: sửa chỉ mục hoặc thống kê, rồi mới OPTION (RECOMPILE) trên thủ tục ít chạy, hoặc ép kế hoạch đã biết là tốt bằng Query Store. Ép kế hoạch là biện pháp giữ tạm đến khi câu SQL hoặc dữ liệu đổi.

Bốn cơ chế biên dịch tự chỉnh, bật theo compatibility level của database:

Cơ chế Có từ Edition Việc làm
Batch mode trên rowstore SQL Server 2019, compatibility 150 Enterprise Câu tổng hợp lớn chạy theo lô dù chưa có columnstore
Nội tuyến hàm vô hướng SQL Server 2019, compatibility 150 Mọi edition Hàm tính từng dòng được kéo vào câu gọi, đỡ gọi hàm một lần mỗi đơn
Memory grant feedback SQL Server 2017 (batch mode), 2019 (row mode) Enterprise Kế hoạch xin thêm hoặc bớt RAM theo các lần chạy trước
Parameter Sensitive Plan SQL Server 2022, compatibility 160 Mọi edition Giữ nhiều kế hoạch cho một câu khi giá trị tham số lệch nhau nhiều

Nâng COMPATIBILITY_LEVEL lên 150 hoặc 160 để các cơ chế này có hiệu lực. Cách đọc chúng trong kế hoạch nằm ở Đọc kế hoạch thực thi. Làm trên bản sao hoặc sau khi Query Store đã bật, để còn thấy câu nào đổi thời gian chạy.

10. Chỉ mục khớp câu hỏi

Câu liệt kê đơn đang mở của một khách chỉ cần mã khách, ngày và tổng tiền. Chỉ mục lọc giữ mỗi dòng TrangThai = 1:

CREATE INDEX IX_DonHang_DangMo
    ON dbo.DonHang (KhachHangId, NgayTao)
    INCLUDE (TongTien)
    WHERE TrangThai = 1
    ON ps_DonHang_Ngay (NgayTao);

INCLUDE để câu dưới lấy đủ cột từ chỉ mục này. ON ps_DonHang_Ngay giữ chỉ mục cùng cách phân vùng với bảng, nếu không SWITCH bị chặn.

SELECT KhachHangId, NgayTao, TongTien
FROM dbo.DonHang
WHERE KhachHangId = 42
  AND TrangThai = 1
  AND NgayTao >= CAST('20260101' AS datetime2(0))
  AND NgayTao < CAST('20270101' AS datetime2(0));

Đổi TrangThai của một đơn làm dòng xuất hiện hoặc biến mất khỏi chỉ mục này. Cột trạng thái bị sửa liên tục trên mọi dòng thì chỉ mục lọc mất lợi và tăng chi phí ghi. Cột TrangThai = 1 chỉ là một phần nhỏ của bảng thì chỉ mục này rất đáng.

Thống kê lỗi thời làm trình tối ưu ước lượng sai số dòng. Lịch UPDATE STATISTICS dbo.DonHang WITH RESAMPLE sau nạp lớn. AUTO_UPDATE_STATISTICS nên để bật. Bảng hàng trăm triệu dòng đôi khi cần AUTO_UPDATE_STATISTICS_ASYNC để câu không phải chờ lần cập nhật thống kê đồng bộ.

11. Lịch sử dòng và hàng đợi đồng bộ

Ba cơ chế cùng trả lời “dữ liệu đã đổi gì”, với độ nặng tăng dần.

Cơ chế Giữ cái gì Khi nào hợp
Change Tracking Khóa của dòng đã đổi kể từ một mốc Ứng dụng tự kéo danh sách đơn cần đồng bộ. Nhẹ
Temporal table Cả dòng tại từng khoảng thời gian Hỏi “lúc 12:00 khách này tên gì”. Có từ SQL Server 2016
Change Data Capture Ảnh trước và sau của dòng, đọc bằng SQL Agent Kho dữ liệu hoặc hệ khác cần nội dung cũ và mới. Không có trên Express

Bảng khách hàng có lịch sử tên:

CREATE TABLE dbo.KhachHang (
    KhachHangId int NOT NULL CONSTRAINT PK_KhachHang PRIMARY KEY CLUSTERED,
    ChiNhanhId int NOT NULL,
    Ten nvarchar(200) NOT NULL,
    SoDienThoai varchar(20) NULL,
    SysStart datetime2 GENERATED ALWAYS AS ROW START NOT NULL,
    SysEnd datetime2 GENERATED ALWAYS AS ROW END NOT NULL,
    PERIOD FOR SYSTEM_TIME (SysStart, SysEnd)
)
WITH (
    SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.KhachHang_LichSu)
);

Sau vài lần UPDATE, câu này trả tên đúng tại thời điểm trước lần sửa:

SELECT KhachHangId, Ten
FROM dbo.KhachHang
FOR SYSTEM_TIME AS OF '2026-10-02T12:00:00'
WHERE KhachHangId = 42;

Bảng lịch sử lớn theo số lần sửa, không theo số khách. Script trên tạo dbo.KhachHang_LichSu trên filegroup mặc định, đang là FG_DATA. Muốn để lịch sử trên FG_ARCHIVE, tạo bảng lịch sử trên filegroup đó trước rồi mới bật SYSTEM_VERSIONING, hoặc chuyển sau bằng clustered index. Temporal không thay log backup: lịch sử nằm trong file dữ liệu và đi theo full backup. Chính sách lọc dòng ở mục sau không tự áp lên bảng lịch sử.

Change Tracking bật ở mức database rồi mức bảng khi chỉ cần biết đơn nào đổi để đồng bộ sang hệ tìm kiếm. CDC khi bên nhận cần cả giá trị TongTien cũ và mới. CDC phụ thuộc SQL Server Agent. Agent dừng thì hàng đợi CDC đứng.

12. Che dữ liệu và khóa theo chi nhánh

Dynamic data masking đổi cách một cột hiện với người không có quyền UNMASK. Người vẫn SELECT được dòng. Đây là lớp hiển thị cho ứng dụng tổng đài, không phải lớp chống người có quyền đọc trang dữ liệu.

ALTER TABLE dbo.KhachHang
    ALTER COLUMN SoDienThoai ADD MASKED WITH (FUNCTION = 'partial(0,"*****",2)');

GRANT SELECT ON dbo.KhachHang TO NhanVienTongDai;
-- Tài khoản ứng dụng quản trị được UNMASK khi cần gọi lại khách.

Người có db_owner hoặc quyền UNMASK thấy số đầy đủ.

Row-level security lọc dòng. Nhân viên mang ChiNhanhId trong SESSION_CONTEXT chỉ thấy khách của chi nhánh đó. Cột ChiNhanhId đã có trên dbo.KhachHang ở script temporal phía trên.

CREATE FUNCTION dbo.fn_KhachHang_ChiNhanh (@ChiNhanhId int)
RETURNS TABLE
WITH SCHEMABINDING
AS
    RETURN
    SELECT 1 AS cho_phep
    WHERE @ChiNhanhId = CAST(SESSION_CONTEXT(N'ChiNhanhId') AS int);
GO

CREATE SECURITY POLICY dbo.pol_KhachHang
ADD FILTER PREDICATE dbo.fn_KhachHang_ChiNhanh (ChiNhanhId)
    ON dbo.KhachHang
WITH (STATE = ON);

Ứng dụng đặt ngữ cảnh sau khi đăng nhập:

EXEC sys.sp_set_session_context @key = N'ChiNhanhId', @value = 3;

Chủ database và người có quyền sửa chính sách vẫn thiết kế được đường đọc khác. Row-level security là kiểm soát trong ứng dụng dùng chung một bảng, không thay quyền GRANT tối thiểu.

Always Encrypted đi xa hơn: driver mã hóa cột trước khi gửi, SQL Server chỉ cất bản mã. Người quản trị database không đọc được số tài khoản. Dùng khi quy định cấm người vận hành xem cột đó. Tìm theo đẳng thức được với mã hóa xác định; tìm theo khoảng giá trị thì không. Phần khóa chính nằm ở ứng dụng hoặc Windows, nên đây là một dự án riêng, không phải một cờ bật trên database.

13. Mã hóa file trên đĩa

TDE mã hóa file dữ liệu, file log và bản backup lúc nằm trên đĩa. Ai chép .mdf hoặc .bak sang máy khác mà không có certificate thì không mở được. Người đã kết nối vào BanHang và có quyền SELECT vẫn đọc đơn bình thường.

Trên SQL Server 2022, Standard có TDE. Trên SQL Server 2019, TDE thuộc Enterprise.

Certificate nằm trong master. Mất certificate là mất khả năng restore sang máy khác, kể cả khi file .bak còn nguyên. Sao lưu certificate ra máy khác, cùng lúc bật TDE, và giữ mật khẩu file khóa riêng.

USE master;
GO

CREATE MASTER KEY ENCRYPTION BY PASSWORD = N'<mat-khau-master-key>';
GO

CREATE CERTIFICATE TdeCert_BanHang
    WITH SUBJECT = N'TDE BanHang';
GO

BACKUP CERTIFICATE TdeCert_BanHang
    TO FILE = N'B:\Keys\TdeCert_BanHang.cer'
    WITH PRIVATE KEY (
        FILE = N'B:\Keys\TdeCert_BanHang.pvk',
        ENCRYPTION BY PASSWORD = N'<mat-khau-file-khoa>'
    );
GO

USE BanHang;
GO

CREATE DATABASE ENCRYPTION KEY
    WITH ALGORITHM = AES_256
    ENCRYPTION BY SERVER CERTIFICATE TdeCert_BanHang;
GO

ALTER DATABASE BanHang SET ENCRYPTION ON;

Đặt mật khẩu thật ở kho mật khẩu, không ghi vào script trong git. Thư mục B:\Keys không nằm trên cùng đĩa với E: và G:.

Mã hóa riêng từng bản backup, khi chưa bật TDE, dùng BACKUP ... WITH ENCRYPTION. Khóa đó cũng phải có trên máy restore.

14. Sẵn sàng cao

Backup giải quyết mất dữ liệu đến mốc sao lưu cuối. Sẵn sàng cao giải quyết thêm thời gian máy đứng. Chọn theo edition và theo số phút được phép mất.

Cách Edition Dữ liệu Máy hỏng thì sao
Full + log backup Standard trở lên cho lịch Agent Một bản Restore, RPO bằng chu kỳ log, RTO bằng thời gian restore
Log shipping Standard, Enterprise Bản log được restore liên tục sang máy phụ Máy phụ mở được sau khi áp nốt log. Thường trễ một chu kỳ log
Failover cluster instance Standard 2 nút, Enterprise nhiều nút hơn Một bản trên đĩa dùng chung Tiến trình SQL chuyển nút. Hỏng đĩa dùng chung thì vẫn phải restore
Basic Availability Group Standard, từ SQL Server 2016 Hai bản, một database, hai replica Tự chuyển khi đồng bộ. Máy phụ không đọc báo cáo được
Availability Group Enterprise Nhiều database, tới nhiều replica Tự chuyển nếu đồng bộ. Replica phụ đọc được nếu cấp quyền

BanHang một database, bản Standard, chấp nhận vài phút mất dữ liệu: log shipping hoặc Basic Availability Group. Cần vừa tự chuyển vừa cho báo cáo đọc trên máy phụ: Availability Group trên Enterprise, replica phụ đồng bộ cho chuyển tự động, replica bất đồng bộ cho báo cáo nếu chấp nhận trễ.

Ứng dụng trở vào listener, không trở vào tên từng máy:

Server=tcp:BanHang-lsn,1433;MultiSubnetFailover=True;ApplicationIntent=ReadWrite

Báo cáo chỉ đọc, trên replica đã cấu hình đọc:

ApplicationIntent=ReadOnly

Availability Group dùng đúng log đã mô tả trong chương lưu trữ: máy phụ redo log của máy chính. Recovery model phải là FULL, và chuỗi log backup vẫn phải chạy. Availability Group không thay backup. Một DELETE nhầm lúc 12:07 được redo sang máy phụ. Muốn lấy lại dữ liệu trước lệnh xóa vẫn restore theo STOPAT.

15. Việc chạy hằng ngày

DBCC CHECKDB đọc cấu trúc trang và đối chiếu chỉ mục. Chạy bản đủ mỗi tuần vào lúc tải thấp. Giữa tuần, trên database lớn, WITH PHYSICAL_ONLY kiểm tra trang hỏng sớm hơn.

DBCC CHECKDB (N'BanHang') WITH NO_INFOMSGS, ALL_ERRORMSGS;

Lỗi CHECKDB được xử lý từ backup lành gần nhất. Chạy CHECKDB xong mà không có backup để restore thì chỉ biết database đã hỏng.

Deadlock đã được session sẵn có system_health ghi lại. Mở file system_health*.xel bằng SSMS khi cần đồ thị deadlock, thay vì chạy Profiler trên production. Profiler thêm tải và không còn là công cụ nên gắn cả ngày.

Cảnh báo SQL Server Agent nên có ít nhất: log dùng quá 80%, lỗi severity 19 đến 25, job backup thất bại, và CHECKDB thất bại. Ngưỡng log gắn với ví dụ chương lưu trữ: log backup 15 phút mà dung lượng dùng vẫn tăng thì tìm giao dịch mở bằng DBCC OPENTRAN.

Ba bộ script cộng đồng được dùng rộng khi vận hành nhiều instance. Đọc script trước khi chạy, ghim một phiên bản, để trong công cụ triển khai nội bộ:

Bộ Việc nó gánh
Ola Hallengren Maintenance Solution Job backup, DBCC CHECKDB, và bảo trì chỉ mục theo kích thước cùng mức phân mảnh
First Responder Kit (sp_Blitz, sp_BlitzCache, sp_BlitzIndex) Lượt xem nhanh cấu hình, câu nặng, và chỉ mục thừa hoặc thiếu
dbatools Backup, restore, sao chép login và di chuyển instance từ PowerShell

Lịch backup trong các bộ này vẫn phải tuân chuỗi full, differential và log của chương lưu trữ, kể cả CHECKSUM và restore thử.

16. Những thói quen nên bỏ

Các việc dưới đây gặp rất thường. Cách làm thay thế nằm ở cột phải.

Thói quen Làm thay bằng
DBCC SHRINKDATABASE mỗi đêm Giữ file đã cấp. Chỉ SHRINKFILE một lần khi vừa xóa một khối lớn và cần trả đĩa, rồi rebuild chỉ mục của file đó vì shrink làm vỡ page
Autogrowth theo phần trăm Bước cố định, như 1 GB với data file và 1 GB với log trong ví dụ BanHang
Rebuild mọi chỉ mục mỗi đêm Rebuild chỉ mục lớn, đo được là thưa hoặc thống kê cũ. Bỏ qua chỉ mục nhỏ
Profiler chạy cả ngày Session Extended Events ngắn, hoặc system_health và Query Store
Linked server trong từng dòng của một vòng lặp Một câu kéo tập khóa cần đồng bộ, xử lý tại chỗ. Linked server để cho tác vụ thưa
SELECT * trong ứng dụng Đúng cột câu đó cần, để chỉ mục phủ và columnstore còn tác dụng
Co file log về 1 MB sau mỗi sự cố đầy log Tìm giao dịch mở hoặc chuỗi log backup đứt, rồi để log ở kích thước đủ cho giờ cao điểm

17. Thứ tự nên bật cho BanHang

  1. Recovery model FULL, lịch full, differential, log, và một lần restore thử có STOPAT. Chi tiết ở chương lưu trữ.
  2. Tách ổ data, log, backup. Cấp sẵn kích thước file. Bật instant file initialization cho tài khoản dịch vụ SQL Server.
  3. Query Store bật trước khi đổi chỉ mục hoặc compatibility level.
  4. Chỉ mục cho câu chạy nhiều nhất, rồi mới nén trang hoặc thêm columnstore cho câu tổng hợp.
  5. READ_COMMITTED_SNAPSHOT khi đo được đọc và ghi chặn nhau.
  6. ADR khi rollback hoặc recovery của giao dịch dài vượt quá mức chấp nhận.
  7. TDE sau khi certificate đã được sao lưu ra ngoài máy.
  8. Log shipping hoặc Availability Group khi RTO của một lần restore full không còn đủ.
  9. DBCC CHECKDB và cảnh báo Agent chạy trước khi thêm tính năng mới.

Trên Azure SQL Database, các bước backup, TDE, ADR, Query Store và RCSI đã do nền tảng bật. Phần đổi vận hành nằm ở Azure SQL.

Đọc tiếp

Mỗi mục của chương này là một bản đồ. Phần đi sâu nằm ở các chương sau:

Nguồn

Đọc tiếp

Bài tiếp theo trong series

Kiểu dữ liệu, collation và khóa chính

Chọn kiểu cho tiền, ngày giờ, chữ tiếng Việt và khóa chính của BanHang dựa trên số byte trên page, cách engine so sánh giá trị, và lỗi mà mỗi lựa chọn sai gây ra.

42 phút đọc

Trong SQL Server

Đọc kế hoạch thực thi

Câu SQL thành kế hoạch thực thi ra sao, lấy kế hoạch ước lượng và thực tế ở đâu, đọc thuộc tính nào trước, và vì sao cùng một thủ tục lúc nhanh lúc chậm.

53 phút đọc