More Related Content Similar to 资源竞争与并发控制 (12) 资源竞争与并发控制1. 资源竞争与并发控制 (1.5小时)
李伟 搜狐视频
weiweili@sohu-inc.com
5. 类似的CAS问题:机票自助选座
多个
AppServ
er怎办?
持久层?
select status from seat where id={:id}
update seat set status={:status} where id={:id}
7. 源代码管理:乐观锁与悲观锁
• CAS问题=>Lost Update Problem
– CVS|SVN(乐观锁):Version Compare
– VSS(悲观锁):编辑前先LOCK代码文件
• Microsoft Virtual SourceSafe
• 乐观锁:其他用户同时修改你正在修改的数据的概率很小
• 读的时候不锁,提交的时候才锁
• 优势:并发度高
• 劣势:不能防止问题发生,但能检测到问题的发生
• 悲观锁:其他用户同时修改你正在修改的数据的概率很大
• 读的时候就锁,提交的时候才释放,期间别人无法读和提交
• 优势:能防止问题发生
• 劣势:并发度低
8. 机票选座解决1:乐观锁
• Update的时候增加“版本对比”
– update seat set status={:1} where
id={:id}
– update seat set status={:1} where
id={:id} and status={:previous version
value, suppose 0}
• 乐观锁:不能防止问题发生,只能检测。
• 乐观锁:并发高峰期(假设打破),产生“抖动”,用户有种被欺
骗的感觉
• Version Compare REFER (版本是一个抽象的概念):
http://jeffkemponoracle.com/2005/10/21/avoiding-lost-
updates-protecting-data-in-a-multi-user-environment/
9. 机票选座解决2:悲观锁
• Select的时候加“锁”
– select status from seat where id={:id}
WITH LOCK(伪代码)
– update seat set status={:1} where
id={:id}
• 互动思考=》先看“事务隔离级别”,后回到这些问题
• InnoDB的Select是否是加锁的?
• 数据库的读操作和写操作是互斥的吗?
• WITH LOCK(伪代码)选:读锁(共享锁)vs. 写
锁(排它锁)?
• 上述两条语句是在一个事务内vs.两个事务内?
11. [w1]悲观锁和乐观锁
并发问题与事务隔离级别
并发问题 脏读(Dirty Read) 不 可复读( Non-Repeatable 幻 读 ( Phantom 丢失更新(Lost Update)
隔离级别 Read) Read)
读 未 提 交 ( Read
Uncommitted)
可能 可能 可能 可能
提 交 就 读 ( Read
Committed)
不可能 可能 可能 可能
可 重 复 读 ( Repeatable
Read)
不可能 不可能 可能 可能
串行化(Serializable)
不可能 不可能 不可能 可能
注1: 黄色表示“问题”;绿色表示“方案”。
注2: ANSI/ISO SQL定义标准,不同数据库产品适当实现,InnoDB全实现了。
注3: http://en.wikipedia.org/wiki/Isolation_%28computer_science%29
13. 并发问题1:脏读(Dirty
Read)
• 大论战:是否应该读取其他事务“未提交” 的
数据(Read Uncommitted vs.
Committed)
14. 并发问题1:脏读(Dirty
Read)
• Read Uncommitted vs. Committed ?
• 脏读(Dirty Read):一个事务读到了其他
“尚未提交” 的数据修改,包括:
(Update,Insert,Delete)
• 隔离级别-Read Committed: 要求只有/只
要“事务提交”了,才/就能读到数据修改。
16. 并发问题2:不可重读(Non-
repetable Read)
• 不可重读:在同一个事务内,前后两次读的
数据不一样(相同查询语句)。
• 隔离级别-Read Committed: 要求只有/只
要“事务提交”了,才/就能读到数据修改。
• 隔离级别-Repeatable READ: 要求只有
“事务提交”了,才能读到数据修改。但并不
是“只要提交了,就一定能读到”(关于
timepoint)。
17. 并发问题3.1:幻读
(Phantom)
http://en.wikipedia.org/wiki/Isolation_%28computer_science%29#Phanto
m_reads
18. 并发问题3.1:wiki错了!
时序 Session A Session B 解说
1 mysql> start transaction; 隔 离 级 别 都 是 :
mysql> select * from employee; REPEATABLE_READ
/*读到Alice和Bob两记录*/
2 mysql> start transaction; 插入ID=3的记录
mysql> insert into employee values(3,'BBB');
mysql> commit;
3 mysql> select * from employee; 没有出现wikipedia说的:“幻读”
/* 读到Alice和Bob两记录*/
4 mysql> select * from employee where id = 3;
Empty set (0.00 sec)
19. 并发问题3.2:幻读
强调:修
(Phantom) 改
REFER: http://cupoy.iteye.com/blog/251796
20. 并发问题3.2:网友错了!
时序 Session A Session B 解说
1 mysql> start transaction; 隔 离 级 别 都 是 :
mysql> select * from employee; REPEATABLE_READ
/*读到Alice和Bob两记录*/
2 mysql> update employee set name = 'AAA';
Rows matched: 2 Changed: 2 Warnings: 0
3 mysql> start transaction; 没有出现网友说的:“幻读”,压
mysql> insert into employee values(3,'333'); 根不能插入
ERROR 1205 (HY000): Lock wait timeout
exceeded; try restarting transaction
4 mysql> select * from employee;
/*读到Alice和Bob两记录*/
21. 并发问题3:真的幻觉了
时序 Session A Session B 解说
1 mysql> start transaction; 隔 离 级 别 都 是 :
mysql> select * from employee; REPEATABLE_READ
/*读到Alice和Bob两记录*/
2 mysql> start transaction; 插入ID=3的记录
mysql> insert into employee values(3,'BBB');
mysql> commit;
3 mysql> select * from employee; 没有出现wikipedia说的:“幻读”
/* 读到Alice和Bob两记录*/
4 mysql> select * from employee where id = 3;
Empty set (0.00 sec)
5 mysql> insert into employee values(3,'3 From A'); ID=3的到底是有,还是没有?幻
ERROR 1062 (23000): Duplicate entry '3' for key 觉了!
'PRIMARY'
24. 实现1-多版本控制:刷新快照
– 提交当前事务,然后开始一个新的事务,并查询
(因为一致性读说的是“在同一事务内,前后两次执
行要一致”,出了这个事务,就不用保证了);
– 采用Locking Reads,也就是说用SELECT…
LOCK IN SHARE MODE或SELECT … FOR
UPDATE。原因:Locking Reads是要锁住被选择
的记录集的,这样其他事务压根就不可能修改这些
数据了,这样连“幻读”都能避免,更不用说“可重读”
了,因此就没必要再用快照来保证“一致性读”了。
简单的说,用Locking Reads来实现“一致性读
(可重读)”是比Consistent Read/None Lock
(即:多版本控制)更为严格的手段,代价是降低
了并发度。
26. 实现2-间隙锁:网友为什么错
1. 锁的不仅仅是结果集的行,还包括间隙
update employee set name = 'AAA';
如果仅仅锁住Alice和Bob两条记录,插入
ID=3的应该是可以,但InnoDB不这样。
2. InnoDB的行锁不是真正的行锁:next-key
(1)index-row locking,基于索引/主键;
(2) gap locking,还要锁住间隙。
27. 实现2-index-row:基于索引
时序 Session A Session B 解说
1.1 mysql> start transaction; 隔 离 级 别 都 是 :
mysql> update employee set name='AAA' REPEATABLE_READ
where id=1;
/*ID=1记录更新为AAA*/
1.2 mysql> start transaction; ID=1锁住;
mysql> update employee set name='BBB' ID=3 没锁;
where id=1; 表明:InnoDB“行锁”
ERROR: Lock wait timeout
mysql> insert into employee values(3,'3
From B');
Query OK, 1 row affected
mysql> commit
删除ID主键后,重做上面的实验
2.1 mysql> start transaction;
mysql> update employee set name='AAA'
where id=1;
/*ID=1记录更新为AAA*/
2.2 mysql> start transaction; ID=1锁住;
mysql> update employee set name='BBB' ID=3锁住;
where id=1; 表明:InnoDB“表锁”
ERROR: Lock wait timeout
mysql> insert into employee values(3,'3
From B');
ERROR: Lock wait timeout
29. 实现2-gap:锁住间隙
时序 Session A Session B 解说
1 mysql> start transaction; mysql> start transaction; 隔 离 级 别 都 是 :
mysql> select * from employee where id > 100 REPEATABLE_READ
for update; /*读到ID=101记录*/ 执行Update也可
2 mysql> update employee set name = 'V101 A' ID=101锁住;
where id = 101; //ERROR: Lock wait ID=102(间隙)锁住;
mysql> insert into employee 锁住范围:(90,+无穷)
values(102,'V102');//Lock wait
mysql> insert into employee values(91,'V191');
//LOCK
mysql> update employee set name = 'V90 A'
where id = 90;
Rows Changed: 1 //OK
mysql> insert into employee values(89,'V89');
//OK
mysql> commit
3 mysql> select * from employee where id > 100
for update; /*读到101记录*/
mysql> select * from employee (for update);
89=V89; 90=V90 A; 101=V101
33. 机票选座解决2:悲观锁
• Select的时候加“锁”
– select status from seat where id={:id}
WITH LOCK(伪代码)
– update seat set status={:1} where
id={:id}
• 问题与回答
• InnoDB的Select是否是加锁的? 一般不,看隔离级别
• 数据库的读操作和写操作是互斥的吗? 不一定,多版本控制
• WITH LOCK(伪代码)选:读锁(共享锁)vs. 写锁(排它
锁)? 写锁,读锁可能导致死锁
• 上述两条语句是在一个事务内vs.两个事务内? 一个事务内,
事务结束时锁会释放
37. 意向锁与多粒度锁系统
• 1、多粒度锁系统:表锁+行锁。
– 表锁:并发低,开销小;
– 行锁:并发高,开销大(每个记录都要登记锁)。
• 2、表中的某个行被锁了,再申请表锁?
– 扫描全表,看是否有某行已锁:效率低。
– 引入“意向锁(Intention)”:对行加锁前,先对表
加意向锁;若表被加意向锁,则表明表中存在某行
正在加锁。
– 申请表锁时,如果表被意向锁了,则拒绝申请。当
然表被锁了,也拒绝申请。