两段锁协议与触发器详解及代码案例

两段锁协议与触发器详解及代码案例

作为数据库工程师面试常考的重点内容,两段锁协议和触发器是必须掌握的核心知识点。下面我将分别详细解释这两个概念,并提供对应的代码案例。

一、两段锁协议 (Two-Phase Locking Protocol)

概念解释

两段锁协议是数据库并发控制中保证事务可串行化的主要方法之一。它要求每个事务的执行分为两个阶段:

  1. 扩展阶段(增长阶段):事务只能获得锁,不能释放任何锁
  2. 收缩阶段(释放阶段):事务只能释放锁,不能获得任何新锁

特点

  • 确保事务调度的可串行化
  • 防止死锁(但需要严格两段锁协议)
  • 可能导致级联回滚问题

两段锁协议的类型

  1. 基本两段锁协议(2PL):仅满足两阶段要求
  2. 严格两段锁协议(Strict 2PL):事务持有的所有排他锁(X锁)必须在事务提交后才释放
  3. 强两段锁协议(Rigorous 2PL):事务持有的所有锁(包括共享锁S锁)必须在事务提交后才释放

代码案例

-- 事务1遵循两段锁协议
BEGIN TRANSACTION;
-- 扩展阶段:获取锁
SELECT * FROM accounts WHERE account_id = 1 FOR UPDATE; -- 获取X锁
SELECT * FROM accounts WHERE account_id = 2 FOR UPDATE; -- 获取X锁

-- 执行转账操作
UPDATE accounts SET balance = balance - 100 WHERE account_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE account_id = 2;

-- 收缩阶段:提交时释放所有锁
COMMIT;

-- 事务2违反两段锁协议(错误示范)
BEGIN TRANSACTION;
SELECT * FROM accounts WHERE account_id = 3 FOR SHARE; -- 获取S锁
UPDATE accounts SET balance = balance - 50 WHERE account_id = 3; -- 需要X锁
-- 在获取新锁前释放了锁(违反2PL)
COMMIT;

二、触发器 (Triggers)

概念解释

触发器是数据库中的一种特殊存储过程,它在特定事件(INSERT、UPDATE、DELETE)发生时自动执行。触发器常用于实现复杂的业务规则、数据完整性和审计跟踪。

触发器组成要素

  1. 触发时机:BEFORE或AFTER
  2. 触发事件:INSERT、UPDATE或DELETE
  3. 触发粒度:行级触发器(FOR EACH ROW)或语句级触发器
  4. 触发条件:WHEN子句指定的条件

触发器类型

  1. DML触发器:响应数据操作语言事件
  2. DDL触发器:响应数据定义语言事件
  3. 登录触发器:响应LOGON事件

代码案例

案例1:审计日志触发器
-- 创建审计表
CREATE TABLE account_audit (
    audit_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    account_id INT NOT NULL,
    changed_by VARCHAR(50) NOT NULL,
    change_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    operation CHAR(1) NOT NULL, -- 'I'=插入, 'U'=更新, 'D'=删除
    old_balance DECIMAL(15,2),
    new_balance DECIMAL(15,2)
);

-- 创建AFTER UPDATE触发器
CREATE OR REPLACE TRIGGER trg_account_audit
AFTER UPDATE ON accounts
FOR EACH ROW
WHEN (OLD.balance <> NEW.balance) -- 仅当余额变化时触发
BEGIN
    INSERT INTO account_audit (
        account_id, 
        changed_by, 
        operation, 
        old_balance, 
        new_balance
    ) VALUES (
        :OLD.account_id, 
        USER, 
        'U', 
        :OLD.balance, 
        :NEW.balance
    );
END;
案例2:数据完整性触发器
-- 确保员工薪资不超过部门预算的BEFORE INSERT/UPDATE触发器
CREATE OR REPLACE TRIGGER trg_check_salary_budget
BEFORE INSERT OR UPDATE OF salary ON employees
FOR EACH ROW
DECLARE
    v_dept_budget DECIMAL(15,2);
    v_total_salaries DECIMAL(15,2);
BEGIN
    -- 获取部门预算
    SELECT budget INTO v_dept_budget
    FROM departments
    WHERE department_id = :NEW.department_id;
    
    -- 计算部门当前总薪资(排除正在修改的员工)
    SELECT NVL(SUM(salary), 0) INTO v_total_salaries
    FROM employees
    WHERE department_id = :NEW.department_id
    AND employee_id <> NVL(:OLD.employee_id, -1);
    
    -- 检查是否超出预算
    IF v_total_salaries + :NEW.salary > v_dept_budget THEN
        RAISE_APPLICATION_ERROR(-20001, 
            '部门预算不足! 预算: ' || v_dept_budget || 
            ', 当前总薪资: ' || v_total_salaries || 
            ', 新增薪资后: ' || (v_total_salaries + :NEW.salary));
    END IF;
END;
案例3:级联更新触发器
-- 当部门ID更新时,自动更新员工记录的部门ID
CREATE OR REPLACE TRIGGER trg_cascade_dept_update
AFTER UPDATE OF department_id ON departments
FOR EACH ROW
BEGIN
    -- 只有当部门ID实际发生变化时才触发
    IF :OLD.department_id <> :NEW.department_id THEN
        UPDATE employees
        SET department_id = :NEW.department_id
        WHERE department_id = :OLD.department_id;
    END IF;
END;

你可能感兴趣的:(数据库,数据库)