MySQL - 锁
1. 锁
提到锁,一般都是因为存在竞争条件(Race Condition)而导致结果不可预测。比如两个线程同时修改某个值,最终这个值的结果是什么有多种可能性。如果加入锁,在线程访问修改某个值的时候,可以禁止其他线程同时访问修改这个值,这样就避免了多种结果的可能性。
MySQL Innodb的锁的最小单位是行锁,基于索引(Index)实现,加锁的时候是在B+树索引结构的节点上加锁。所以如果某个字段没有索引,然后用户通过这个字段进行查找来更新数据的时候就会导致整个表(其实是整个聚类索引的叶子结点)被加锁;如果用户通过聚类索引的字段进行查找,则只对索引查找结果的叶子节点加锁;如果用户通过二级索引字段进行查找,则除了对二级索引加锁,还会对聚类索引加锁。
2. 事务
事务具有ACID特性:分别是原子性(atomicity),一致性(consistency),隔离性(isolation),和持久性(durability)。
原子性(atomicity)是指一个事务内的操作要不全被commit,要不整个rollback。
一致性(consistency)是指系统始终处于一致的状态,事务操作只会使系统从一个状态变幻到另一个一致的状态。
隔离性(isolation)是指各个事务之间是独立的,隔离的,彼此不会相互影响。
持久性(durability)是指事务提交之后数据持久保存在数据库系统中。
3. 数据库的隔离级别
数据库的隔离级别(Isolation level)分为:未提交读(Uncommit Read),提交读(Commit Read),可重复读(Repeatable Read)和可串行化(Serializable)。
在了解隔离级别之前先来看看InnoDB读的分类:快照读(snapshot read)和现读(current read)。
一般的SELECT
都是快照读,其他的读都属于现读,比如SELECT * FROM tableName FOR SHARE
。快照读不加锁,根据隔离级别的不同读取策略也不同;现读会加S锁(共享锁)或者X锁(互斥锁),其中SELECT ... FOR SHARE
会加S锁,其他情况都是加X锁。
未提交读(Uncommit Read)就是快照读可以读取事务内修改了但是未提交的值,这种情况下就会产生脏读。一般不会考虑设置为数据库隔离级别。
提交读(Commit Read)就是快照读只可以读取提交之后的数据。这样就消除了脏读的产生,但是会有幻读产生的可能性。所谓幻读就是本来要修改了年龄等于20的用户的表信息,事务提交之后发现有些“没有修改成功”,因为有线程在上述事务期间插入了年龄为20的新用户信息,这样会有一种操作未成功的错觉。是MySQL的默认隔离级别。
可重复读(Repeatable Read)的隔离级别下除了一般的行锁,也会同时加上间隙锁(gap lock)。间隙锁防止了幻读的产生:上例中age为20的B+树节点之间和范围前后节点之间也会被加锁,导致age为20的新数据无法插入。
可串行化(Serializable)的隔离级别下,快照读不复存在,所有的读都是现读。读写冲突,导致性能下降。一般不会考虑设置为数据库隔离级别。
4. 死锁以及如何避免死锁
Reference
hedengcheng.com/?p=771