一致性 Consistency 含义

一致性 Consistency 含义

什么是一致性

一致性是 ACID 的核心目标:事务执行前后,数据库必须始终处于一致状态

-- 转账场景
START TRANSACTION;

UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

COMMIT;
事务前:id=1 余额 500, id=2 余额 300, 总余额 800
事务后:id=1 余额 400, id=2 余额 400, 总余额 800 ✅ 一致

一致性不是 InnoDB 独有的,它由应用层、数据库约束、ACID 中的 AID共同保证。

一致性的两层含义

1. 数据库层面的约束一致性

数据库本身强制规则:

-- 主键约束
CREATE TABLE user (id INT PRIMARY KEY);
INSERT INTO user VALUES (1);
INSERT INTO user VALUES (1);  -- ❌ 违反主键唯一性

-- 唯一约束
ALTER TABLE user ADD UNIQUE (email);
INSERT INTO user VALUES (1, 'a@b.com');
INSERT INTO user VALUES (2, 'a@b.com');  -- ❌ 违反唯一约束

-- 外键约束
CREATE TABLE orders (
    user_id INT,
    FOREIGN KEY (user_id) REFERENCES user(id)
);
INSERT INTO orders VALUES (999);  -- ❌ user_id 999 不存在

-- NOT NULL 约束
INSERT INTO user (id) VALUES (3);  -- 如果 name 是 NOT NULL → ❌

-- CHECK 约束(8.0+)
CREATE TABLE account (
    balance DECIMAL(10,2) CHECK (balance >= 0)
);
INSERT INTO account VALUES (-100);  -- ❌ 违反 CHECK

2. 业务逻辑层的一致性

数据库无法自动确保,需要应用层实现:

-- 业务规则:转账不能产生负数
START TRANSACTION;

-- 需要应用层先检查
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
-- 如果 balance < 100,应该回滚事务
-- 如果 balance >= 100,执行转账

UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;

COMMIT;

一致性由 ACID 共同协作保障

原子性(A) → 事务不会半途而废 → 避免了部分提交
  │
  ├─ 转账到一半系统崩溃
  ├─ ROLLBACK → 回到转账前状态 → 数据仍然一致
  └─ 不会出现"扣了钱但没到账"的不一致

隔离性(I) → 并发事务互不干扰 → 避免脏读/幻读
  │
  ├─ 两个线程同时卖最后一张票
  ├─ 隔离性保证一个成功一个失败
  └─ 不会出现超卖的不一致

持久性(D) → 提交后数据不丢失 → 已确认的修改有效
  │
  ├─ 转账 COMMIT 成功后系统崩溃
  ├─ 重启后余额变化还在
  └─ 不会出现"用户确认了但钱没了"的不一致

一致性与隔离级别的选择

不同隔离级别对”一致性”的保护程度不同:

-- READ UNCOMMITTED(最弱一致性)
-- 可能读到脏数据
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
SELECT balance FROM accounts;  -- 可能读到未提交的修改
-- 然后基于这个"不准确"的余额做决策 → 业务不一致

-- REPEATABLE READ(MySQL 默认,较强一致性)
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT balance FROM accounts;  -- 始终看到事务开始时的快照
-- 一致性更强,但并发性能稍弱

一致性的典型破坏场景

场景一:并发更新丢失

-- 两个用户同时修改同一条记录
-- 用户 A:把 name 改为 '张三'
UPDATE user SET name = '张三' WHERE id = 1;

-- 用户 B:把 name 改为 '李四'
UPDATE user SET name = '李四' WHERE id = 1;

-- 最终结果:'李四'(用户 A 的更新丢失了)
-- 这不叫一致性破坏,这叫"更新覆盖"
-- 真正的一致性破坏是指约束被违反

场景二:违反业务约束

-- 表无 CHECK 约束
CREATE TABLE accounts (
    id INT PRIMARY KEY,
    balance DECIMAL(10,2)  -- 没有 CHECK
);

-- 应用层也没检查
UPDATE accounts SET balance = balance - 500 WHERE id = 1;
-- balance 变成 -100 → 业务系统不一致

-- 解决方案:
-- 1. 加 CHECK (balance >= 0) → 数据库层面
-- 2. 应用层 SELECT ... FOR UPDATE → 应用层面
-- 3. 两者都做 → 最安全

场景三:缺乏事务的并发

-- 没有事务的并发操作
-- 线程 A
UPDATE orders SET status = 'shipped' WHERE id = 100;

-- 线程 B(同时)
UPDATE orders SET status = 'cancelled' WHERE id = 100;

-- 如果中间有业务逻辑依赖 status 的"当前值"
-- 没有锁保护就会出错

如何确保一致性

-- 1. 使用事务包裹操作
START TRANSACTION;
-- 操作...
COMMIT;

-- 2. 数据库约束
-- NOT NULL、UNIQUE、FOREIGN KEY、CHECK

-- 3. 适当隔离级别
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 4. 应用层校验
if (balance < amount) {
    throw new Exception("余额不足");
    rollback();
}

-- 5. 使用锁保护关键资源
SELECT ... FOR UPDATE;  -- 悲观锁
-- 或乐观锁版本号机制

面试要点

  • 一致性是所有 ACID 的核心目标:A+I+D 共同服务 C
  • 数据库约束一致性:主键、唯一、外键、CHECK —— 数据库自动保证
  • 业务逻辑一致性:余额不能为负、库存不能超卖 —— 应用层必须保证
  • 隔离级别影响一致性:隔离越弱,越容易读到”不一致”的数据
  • 真正的一致性是”状态合法”:数据不违反任何约束规则

一句话总结:一致性是 ACID 的”最终目的”——确保数据始终符合所有规则和约束;原子性、隔离性、持久性是它的三个保障手段,A(要么全做要么全不做)+ I(并发不打架)+ D(做完不丢)= C。

© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容