一致性 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


暂无评论内容