Concurrency Control: Transaction Isolation
其实,并不存在完美的持久性
事务提交后,即使存在故障,事务写入的任何数据不会丢失
原子代表不可分割
包含了多个写操作的事务,如果事务终止,则会撤销所有的修改。 要不全部成功,要么全部失败。
原子性可以简化事务的重试
隔离性其实含义更接近 可序列化 隔离级别
并发执行的多个事务相互隔离,不能相互交叉。(可串行化)
通俗讲: 就是多个事务不能相互影响,就像一个接一个执行一样。
对数据有特定的预期状态,任何数据必须满足需求
注意:
比如: 充值 = 余额 + 消费
多个事务同时并发执行时,发生的错误
脏读 (dirty read)
一个事务读取了另一个并行未提交事务写入的数据。
不可重复读 (nonrepeatable read)
一个事务重新读取之前读取过的数据,发现该数据已经被另一个事务(在初始读之后提 交)修改。
幻读 (phantom read)
一个事务重新执行一个返回符合一个搜索条件的行集合的查询, 发现满足条件的行集合 因为另一个最近提交的事务而发生了改变。
序列化异常 (serialization anomaly)
成功提交一组事务的结果与这些事务所有可能的串行执行结果都不一致。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 序列化异常 |
|---|---|---|---|---|
| 读未提交 | 允许 | 可能 | 可能 | 可能 |
| 读已提交 | 不可能 | 可能 | 可能 | 可能 |
| 可重复读 | 不可能 | 不可能 | 允许 | 可能 |
| 可序列化 | 不可能 | 不可能 | 不可能 | 不可能 |
set [global | session] transaction isolation level xxxxx;
set global transaction isolation level serializable;
set session transaction isolation level read committed;
set transaction isolation level repeatable read;
注意
并发事务,事务B 读到了 事务A,已写入 未提交 的数据
begin;
update users set age = 18;
--- 这里发生脏读,会出现 有一个18岁,但是18岁统计值 为0的矛盾数据
update state set age_18_count = age_18_count + 1;
commit;
两个事务,都没有提交时,修改了相同数据,后写入的操作,会覆盖早的写入
会带来的问题
如果两个事务,需要同时更新多行数据,会产生错误结果
举个例子
begin; begin;
update car set user = 'dalei' where id = 2;
update `key` set user = 'mifa' where id=1;
update car set user = 'mifa' where id=2;
update `key` set user = 'dalei' where id = 1;
commit; commit;
结果是:dalei有🔑,mifa有🚗
策略
只读已经提交的数据
实现
(开始有点MVCC的味道了)
策略
为了预防脏写,一般推迟第二个写操作,直到前面的事务完成
( 卡住 )
实现
数据库会自动加上行锁
读不阻塞写,写不阻塞读
一个事务重新读取之前读取过的数据,发现该数据已经被另一个事务(在初始读之后提 交)修改。
例子: mifa给dalei转账5元, 开始均有10块钱
begin;
select balance from account where name='dalei'; -- 10
begin;
update account set balance = balance + 5 where name = 'dalei';
update account set balance = balance - 5 where name = 'mifa';
commit;
select balance from account where name='mifa'; -- 5
commit;
奇怪🤔:怎么只有15 = 10 + 5
策略
每个事务,一开始只能看到最近提交的数据, 即使后来被改了,还是只能看到老数据
实现
写操作需要加写锁, 防止脏写 读数据不加锁,数据库保留了对象的不同版本
与读已提交不同点
一般都是 读-修改-写回 引发的丢失问题
常用的方法
update account set balance = balance + 10 where user='dalei'select * from account where name = 'dalei' for update注意:
对于并发更新
可重读隔离级别下, 过年增加年龄
begin;
select age from users where name='dalei' for update; -- 18
begin;
select age from users where name='dalei'; -- 18
select age from users where name='dalei' for update; -- pg 会中止事物,mysql?
select age from users where name='dalei'; -- 值是多少?
update users set age = age + 1 where name='dalei';
commit;
update users set age = age + 1 where name='dalei';
commit;
问题
上面👆pg会报:
could not serialize access due to concurrent update
读阻塞写,写阻塞读
两个事务同时更新不同对象
例子,两个人同时想退出admin, 系统至少有一个admin (注意pg可重读没这个问题)
begin;
select count(*) from users where role='admin';
begin;
select count(*) from users where role='admin';
update users set role='normal' where name='enlai'; -- count > 1
commit;
update users set role='normal' where name='dalei'; -- count > 1
commit;
fuck, 没有管理员了!
备注:
写倾斜模式
解决办法
有些情况,加锁不能解决问题
东亮的问题, 需要插入数据
BEGIN;
-- 步骤1: 检查会议室有空吗
SELECT COUNT(*) FROM bookings WHERE room_id = 123 AND end_time > '2015-01-01 12:00' AND start_time < '2015-01-01 13:00';
-- 步骤2: 如果👆返回0
-- 步骤3: 插入记录
INSERT INTO bookings (room_id, start_time, end_time, user_id) VALUES (123, '2015-01-01 12:00', '2015-01-01 13:00', 666);
COMMIT;
2PL
面试问题:next-key locking?