SQL Server 2022 新特性:Ledger 账本表详解(防篡改审计利器)

前言

数据安全与审计合规是企业数据库管理的核心需求之一。传统方案依赖触发器、CDC(变更数据捕获)或第三方审计工具,配置复杂、维护成本高,且难以从根本上防止具有高权限的 DBA 篡改历史记录。

SQL Server 2022 引入了 Ledger(账本) 功能------一种基于区块链哈希链思想的数据库级防篡改机制。它能够:

  • 自动记录所有 INSERT / UPDATE / DELETE 操作的完整历史
  • 防止篡改:即使 DBA 也无法静默修改历史记录
  • 提供加密验证:通过哈希摘要证明数据完整性
  • 满足合规要求:金融、医疗、审计等场景开箱即用

本文通过完整示例,带你掌握 Ledger 账本表的核心用法。


准备测试数据

复制代码
-- 创建测试数据库(启用 Ledger 功能)
CREATE DATABASE LedgerDemo;
GO

USE LedgerDemo;
GO

-- 创建"可更新账本表":记录员工工资信息
CREATE TABLE dbo.EmployeeSalary
(
    EmployeeID   INT           NOT NULL PRIMARY KEY,
    EmployeeName NVARCHAR(50)  NOT NULL,
    Department   NVARCHAR(30)  NOT NULL,
    Salary       DECIMAL(10,2) NOT NULL,
    UpdatedBy    NVARCHAR(50)  NOT NULL DEFAULT SYSTEM_USER
)
WITH (SYSTEM_VERSIONING = ON, LEDGER = ON);
GO

-- 插入初始员工薪资数据
INSERT INTO dbo.EmployeeSalary (EmployeeID, EmployeeName, Department, Salary, UpdatedBy)
VALUES
    (1001, N'张伟',   N'研发部', 18000.00, N'HR_Admin'),
    (1002, N'李娜',   N'财务部', 15000.00, N'HR_Admin'),
    (1003, N'王强',   N'销售部', 12000.00, N'HR_Admin'),
    (1004, N'赵雷',   N'研发部', 20000.00, N'HR_Admin'),
    (1005, N'陈晓燕', N'人事部',  9800.00, N'HR_Admin');
GO

说明WITH (SYSTEM_VERSIONING = ON, LEDGER = ON) 是创建可更新账本表的关键语法。SQL Server 会自动生成配套的历史表和账本视图。


示例一:查看账本表的自动生成结构

复制代码
-- 查看 Ledger 相关系统对象
SELECT 
    t.name                        AS 表名,
    t.ledger_type_desc            AS Ledger类型,
    t.ledger_view_name            AS 账本视图名,
    t.history_table_name          AS 历史表名
FROM sys.tables t
WHERE t.is_ledger_on = 1;
GO

-- 查看账本视图的列结构
SELECT 
    COLUMN_NAME         AS 列名,
    DATA_TYPE           AS 数据类型,
    IS_NULLABLE         AS 可空
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'MSSQL_LedgerHistoryFor_EmployeeSalary'
   OR TABLE_NAME LIKE '%EmployeeSalary_Ledger%'
ORDER BY ORDINAL_POSITION;
GO

查询结果(sys.tables 账本信息):

表名 Ledger类型 账本视图名 历史表名
EmployeeSalary UPDATABLE_LEDGER_TABLE EmployeeSalary_Ledger MSSQL_LedgerHistoryFor_EmployeeSalary

说明:SQL Server 自动创建了:

  • 历史表MSSQL_LedgerHistoryFor_EmployeeSalary(存储旧版本记录)
  • 账本视图EmployeeSalary_Ledger(合并当前+历史,含操作类型标记)

示例二:执行数据变更并查看账本视图

复制代码
-- 模拟业务操作:调整张伟的薪资(涨薪)
UPDATE dbo.EmployeeSalary
SET Salary = 21000.00, UpdatedBy = N'Manager_Liu'
WHERE EmployeeID = 1001;
GO

-- 模拟:李娜离职,删除记录
DELETE FROM dbo.EmployeeSalary
WHERE EmployeeID = 1002;
GO

-- 模拟:新员工入职
INSERT INTO dbo.EmployeeSalary (EmployeeID, EmployeeName, Department, Salary, UpdatedBy)
VALUES (1006, N'刘洋', N'研发部', 19500.00, N'HR_Admin');
GO

-- 查询账本视图,查看完整操作历史
SELECT
    EmployeeID,
    EmployeeName,
    Department,
    Salary,
    UpdatedBy,
    ledger_operation_type_desc  AS 操作类型,
    ledger_transaction_id       AS 事务ID,
    ledger_sequence_number      AS 序列号
FROM dbo.EmployeeSalary_Ledger
ORDER BY ledger_transaction_id, ledger_sequence_number;
GO

账本视图查询结果:

EmployeeID EmployeeName Department Salary UpdatedBy 操作类型 事务ID 序列号
1001 张伟 研发部 18000.00 HR_Admin INSERT 101 0
1002 李娜 财务部 15000.00 HR_Admin INSERT 101 1
1003 王强 销售部 12000.00 HR_Admin INSERT 101 2
1004 赵雷 研发部 20000.00 HR_Admin INSERT 101 3
1005 陈晓燕 人事部 9800.00 HR_Admin INSERT 101 4
1001 张伟 研发部 18000.00 HR_Admin DELETE 102 0
1001 张伟 研发部 21000.00 Manager_Liu INSERT 102 1
1002 李娜 财务部 15000.00 HR_Admin DELETE 103 0
1006 刘洋 研发部 19500.00 HR_Admin INSERT 104 0

说明 :UPDATE 操作在账本视图中被分解为 DELETE(旧值)+ INSERT(新值) 两条记录,确保每一次变更都有迹可查。


示例三:溯源查询------还原员工薪资变更历史

复制代码
-- 查询员工 1001(张伟)的完整薪资变更轨迹
SELECT
    EmployeeID,
    EmployeeName,
    Salary                      AS 薪资,
    UpdatedBy                   AS 操作人,
    ledger_operation_type_desc  AS 操作类型,
    ledger_transaction_id       AS 事务ID
FROM dbo.EmployeeSalary_Ledger
WHERE EmployeeID = 1001
ORDER BY ledger_transaction_id, ledger_sequence_number;
GO

-- 查询被删除的员工记录(在账本视图中仍保留)
SELECT
    EmployeeID,
    EmployeeName,
    Department,
    Salary,
    ledger_operation_type_desc AS 操作类型,
    ledger_transaction_id      AS 事务ID
FROM dbo.EmployeeSalary_Ledger
WHERE ledger_operation_type_desc = 'DELETE'
ORDER BY ledger_transaction_id;
GO

张伟薪资变更轨迹:

EmployeeID EmployeeName 薪资 操作人 操作类型 事务ID
1001 张伟 18000.00 HR_Admin INSERT 101
1001 张伟 18000.00 HR_Admin DELETE 102
1001 张伟 21000.00 Manager_Liu INSERT 102

说明:通过账本视图,可以清楚地看到张伟薪资从 18000 变更为 21000 的完整过程,包括操作人。即使没有额外审计日志,也能完整还原历史。


示例四:防篡改验证------生成并核验数据库摘要

复制代码
-- 生成数据库账本摘要(用于后续验证数据完整性)
EXECUTE sp_generate_database_ledger_digest;
GO

摘要输出示例(JSON格式):

json

复制

复制代码
{
    "database_name": "LedgerDemo",
    "block_id": 4,
    "hash": "0x3F8A2B1C...",
    "last_transaction_commit_time": "2026-04-21T08:45:12",
    "digest_version": 1
}

-- 验证账本完整性(检测是否有人在数据库外部直接修改数据)
DECLARE @digest NVARCHAR(MAX) = N'{
    "database_name": "LedgerDemo",
    "block_id": 4,
    "hash": "0x3F8A2B1C...",
    "last_transaction_commit_time": "2026-04-21T08:45:12",
    "digest_version": 1
}';

EXECUTE sp_verify_database_ledger @digest;
GO

验证结果(数据未被篡改时):

消息
Ledger verification successful.

说明

  • sp_generate_database_ledger_digest:生成包含哈希摘要的 JSON,应定期保存到外部安全存储(如 Azure Blob、AWS S3 或本地文件)
  • sp_verify_database_ledger:用保存的摘要验证当前数据库状态,如果有人直接绕过 SQL Server 修改底层数据文件,验证将失败

示例五:仅追加账本表(Append-only Ledger)

复制代码
-- 创建"仅追加账本表":适合日志、审计流水等只写场景
CREATE TABLE dbo.AuditLog
(
    LogID       INT IDENTITY(1,1) PRIMARY KEY,
    EventTime   DATETIME2        NOT NULL DEFAULT SYSDATETIME(),
    EventType   NVARCHAR(50)     NOT NULL,
    Operator    NVARCHAR(50)     NOT NULL,
    Description NVARCHAR(200)    NOT NULL
)
WITH (LEDGER = ON (APPEND_ONLY = ON));
GO

-- 插入审计日志
INSERT INTO dbo.AuditLog (EventType, Operator, Description)
VALUES
    (N'LOGIN',  N'zhang_wei', N'用户从 192.168.1.101 登录系统'),
    (N'EXPORT', N'li_na',     N'导出财务报表 2026Q1'),
    (N'CONFIG', N'admin',     N'修改系统配置:MaxConnections=500');
GO

-- 尝试更新(会被拒绝)
-- UPDATE dbo.AuditLog SET EventType = N'MODIFIED' WHERE LogID = 1;
-- 报错:Cannot update or delete from a ledger table that is append-only.

-- 查询审计日志(含账本元数据)
SELECT
    LogID,
    EventTime,
    EventType,
    Operator,
    Description,
    ledger_transaction_id  AS 事务ID,
    ledger_sequence_number AS 序列号
FROM dbo.AuditLog
ORDER BY LogID;
GO

审计日志查询结果:

LogID EventTime EventType Operator Description 事务ID 序列号
1 2026-04-21 08:45:01 LOGIN zhang_wei 用户从 192.168.1.101 登录系统 105 0
2 2026-04-21 08:45:01 EXPORT li_na 导出财务报表 2026Q1 105 1
3 2026-04-21 08:45:02 CONFIG admin 修改系统配置:MaxConnections=500 106 0

说明 :仅追加账本表(APPEND_ONLY = ON)禁止 UPDATE 和 DELETE,适合审计流水、操作日志等场景,从语法层面彻底杜绝历史记录被删改。


与旧版写法对比

对比维度 传统方案(触发器/CDC) SQL Server 2022 Ledger
配置复杂度 高(需手动创建触发器或配置 CDC) 低(建表时加 LEDGER = ON 即可)
防 DBA 篡改 ❌ 不能(DBA 可禁用触发器/修改历史表) ✅ 能(哈希链保护,篡改可被检测)
历史查询 需手动设计历史表结构 自动生成历史表和账本视图
完整性证明 ❌ 无法提供密码学证明 ✅ 支持哈希摘要验证(可存外部)
UPDATE 展现方式 依实现而异 统一为 DELETE + INSERT 记录
仅追加场景 需额外逻辑控制 APPEND_ONLY = ON 原生支持
性能影响 触发器有较大开销 系统版本控制,开销较小
代码维护成本 几乎为零

旧写法(触发器模拟审计):

复制代码
-- 传统:用触发器记录薪资变更(繁琐且可被 DBA 绕过)
CREATE TABLE dbo.SalaryAudit_Old
(
    AuditID    INT IDENTITY PRIMARY KEY,
    EmployeeID INT,
    OldSalary  DECIMAL(10,2),
    NewSalary  DECIMAL(10,2),
    ChangedAt  DATETIME2 DEFAULT SYSDATETIME(),
    ChangedBy  NVARCHAR(50) DEFAULT SYSTEM_USER
);
GO

CREATE TRIGGER trg_SalaryChange
ON dbo.EmployeeSalary_Old
AFTER UPDATE
AS
BEGIN
    INSERT INTO dbo.SalaryAudit_Old (EmployeeID, OldSalary, NewSalary)
    SELECT d.EmployeeID, d.Salary, i.Salary
    FROM deleted d
    JOIN inserted i ON d.EmployeeID = i.EmployeeID
    WHERE d.Salary <> i.Salary;
END;
GO
-- 问题:DBA 可以执行 DISABLE TRIGGER 或直接修改 SalaryAudit_Old 表

新写法(Ledger 账本表):

复制代码
-- SQL Server 2022:建表时直接启用 Ledger,无需任何触发器
CREATE TABLE dbo.EmployeeSalary
(
    EmployeeID   INT           NOT NULL PRIMARY KEY,
    EmployeeName NVARCHAR(50)  NOT NULL,
    Salary       DECIMAL(10,2) NOT NULL
)
WITH (SYSTEM_VERSIONING = ON, LEDGER = ON);
-- 历史记录、账本视图、哈希保护:全部自动就绪

兼容性说明

版本 支持情况
SQL Server 2022(16.x) ✅ 完整支持(可更新账本表 + 仅追加账本表 + 摘要验证)
SQL Server 2019 及以下 ❌ 不支持
Azure SQL Database ✅ 完整支持(与 SQL Server 2022 同步)
Azure SQL Managed Instance ✅ 支持

注意事项:

  1. 数据库兼容级别:Ledger 功能不依赖兼容级别,只要版本是 SQL Server 2022 即可使用
  2. 不可逆性 :账本表一旦创建,不能通过 ALTER TABLE 关闭 Ledger 属性
  3. 历史表限制 :系统自动生成的历史表(MSSQL_LedgerHistoryFor_*)不能直接删除,只能通过删除主表来删除
  4. 仅追加表限制APPEND_ONLY = ON 的账本表不支持 SYSTEM_VERSIONING,两者互斥
  5. 摘要存储 :强烈建议将 sp_generate_database_ledger_digest 的输出定期保存到 数据库外部(Azure Blob/本地文件系统/不可变存储),否则摘要本身也可能被篡改

总结

SQL Server 2022 Ledger 账本表为数据防篡改审计提供了原生的、低成本的解决方案:

特性 要点
两种类型 可更新账本表(LEDGER = ON)和仅追加账本表(APPEND_ONLY = ON
自动历史追踪 UPDATE 自动拆分为 DELETE + INSERT,完整保留变更前后值
防篡改机制 哈希链 + 摘要验证,即使 DBA 也无法静默修改历史
账本视图 自动生成,合并当前数据与历史,直接查询即可溯源
合规场景 金融流水、薪酬记录、操作日志、医疗数据等强审计场景

核心建议:在有审计合规要求的业务表上,用 WITH (SYSTEM_VERSIONING = ON, LEDGER = ON) 替代传统触发器方案,配合定期保存摘要,即可构建可信的数据防篡改体系。

相关推荐
andafaAPS1 小时前
安达发|工艺品aps自动排产排程排单软件:告别生产“一团乱麻“
大数据·数据库·人工智能·安达发aps·计划排产软件·自动排单软件
zt1985q1 小时前
本地部署源代码管理解决方案 Bitbucket Data Center 并实现外部访问
运维·服务器·数据库·网络协议·postgresql·源代码管理
一只专注api接口开发的技术猿2 小时前
OpenClaw 对接淘宝商品 API,低成本实现全天候选品监控|附可运行 Python 实操代码
大数据·开发语言·数据库·python
爱喝水的鱼丶2 小时前
SAP-ABAP:SAP基础数据校验工具开发系列博客(共5篇)第三篇:SAP接口对接开发:实现数据的实时/批量校验交互
运维·数据库·学习·性能优化·sap·abap·经验交流
真香号3 小时前
记一次生产RocketMQ消息积压消费慢的排查与解决
数据库·rocketmq·java-rocketmq
数据库小学妹3 小时前
国产数据库技术成熟度实测:从Oracle兼容到高可用,四个维度评估能不能上生产
数据库·经验分享·oracle·性能优化·dba
JdSnE27zv3 小时前
数据库性能优化三:程序操作优化
数据库·sql·性能优化
AC赳赳老秦4 小时前
OpenClaw任务复盘自动化:统计每日完成工作、遗留问题,优化工作节奏
java·大数据·linux·运维·服务器·数据库·openclaw
AOwhisky4 小时前
学习自测(MySQL系列第一期、第二期)
linux·运维·数据库·学习·mysql·云计算
我叫张小白。5 小时前
Redis BitMap实现用户签到功能
数据库·redis·缓存·fastapi