锁的分类
- 按锁的粒度划分:表级锁,行级锁,页级锁
- 按锁级别划分:共享锁,排它锁
- 按加锁方式划分:自动锁,显式锁
- 按操作划分:DML锁,DDL锁
- 按使用方式划分:乐观锁,悲观锁
MyISAM与InnoDB关于锁方面的区别
- MyISAM
MyISAM
- 不支持事务
- 默认用的是表级锁(跟索引无关),不支持行级锁
- 不会出现死锁(MyISAM总是一次获得SQL语句所需要的全部锁。这也正是MyISAM表不会出现死锁(Deadlock Free)的原因)
- 在执行LOCK TABLES后,只能访问显式加锁的这些表,不能访问未加锁的表;同时,如果加的是读锁,那么只能执行查询操作,而不能执行更新操作。
- 不同的操作间是串行的
- 并发插入(可在上述链接中查找):允许一个进程读表的同时,另一个进程从表尾插入
- 锁调度(可在上述链接中查找):MySQL认为写请求一般比读请求要重要(这也正是MyISAM表不太适合于有大量更新操作和查询操作应用的原因)
适用场景:
- 频繁执行全表count语句(MyISAM用一个变量存储了数据行数)
- 对数据进行增删改的频率不高(锁住整张表,冲突多),查询非常频繁
- 没有事务
锁模式:
- 表共享读锁(Table Read Lock)
- 表独占写锁(Table Write Lock)
加锁方式:
- 排它锁:select …for update
- 共享锁:select … lock in share mode
- update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型
- InnoDB
InnoDB
- 支持事务
- 默认用的是行级锁(InnoDB行锁是通过给索引上的索引项加锁来实现的,当不走索引时,采用的是表级锁,用到索引的时候使用行级锁),也支持表级锁
- 会出现死锁
适用场景:
- 数据增删改查都相当频繁
- 可靠性要求比较高,要求支持事务
锁模式:
- 共享锁(S):又称读锁
- 排他锁(X):又称写锁,允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享读锁和排他写锁
为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁
- 意向锁的意义
- 意向共享锁(IS):事务打算给数据行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁
- 意向排他锁(IX):事务打算给数据行加排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁
加锁方式:
- 共享锁(S):select … lock in share mode
- 排他锁(X):select …for update
- 意向锁是InnoDB自动加的,不需用户干预,就是说,事务A申请一行的行锁的时候,数据库会自动先开始申请表的意向锁,不需要我们程序员使用代码来申请。
- 对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁
数据库事务的四大特性
可在上述链接中查找:
- 原子性,一致性,隔离性,持久性
事务隔离级别以及各级别下的并发访问问题
- 并发事务带来的问题
- 更新丢失:mysql所有事务隔离级别在数据库层面上均可避免
- 脏读(一个事务读到另一个事务未提交的内容,如果事务发生回滚,会产生错误):read-committed事务隔离级别以上可避免
- 不可重复读(多次读取数据不一致,一个事务读取数据中,另一个事务对其读取数据发生修改并提交,导致多次读取不一致):repeatable-read(默认)事务隔离级别以上可避免(多次读取数据一直,但更新操作仍是建立在其他事务修改之后的结果上进行操作)
- 幻读(增/删):serializable事务隔离级别可避免
- 可重复读理论上不可避免幻读,mysql采用一些技巧避免了
- 脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。
- 数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上 “串行化”进行,这显然与“并发”是矛盾的。
- select @@tx_isolation:查看事务的隔离级别
- set session transaction isolation level …:设置隔离级别
InnoDB(密集索引)可重复读隔离级别下如何避免幻读
- 表象:快照读(非阻塞读)–伪MVCC(数据多版本并发控制 MultiVersion Concurrency Control)
虽然不会出现幻读,但其读到的仅仅是之前版本的快照,掩耳盗铃
- 内在:next-key锁(行锁+gap锁)
Gap锁(间隙锁)
- 仅存在与 rr 级别以及之上
区间(前开后闭):(10,11,13,20)
- (negative infinity, 10]
- (10, 11]
- (11, 13]
- (13, 20]
- (20, positive infinity)
对主键索引或者唯一索引会用Gap锁吗
- 如果where条件全部命中,则不会用Gap锁,只会用记录所
- 如果where条件部分命中或者全不命中,则会加Gap锁
– 如果是部分命中的话,只会部分加锁
– 对于非主键索引,会对主键一起添加Gap锁(InnoDB采用的是密集索引)
Gap锁会用在非唯一索引或者不走索引的当前读中
- 非唯一索引
– 只需在修改的周边上Gap锁,区间如上所述
– 通过比较主键可以更精确范围- 不走索引
– 所有Gap都上锁,类似于表锁
RC,RR级别下的InnoDB的非阻塞读(快照读)如何实现
- 当前读和快照读
数据库实现事务隔离的方式,基本可以分为以下两种:
当前读:读取的记录是最新版本,读取之后还需保证其他并发事务不能修改当前记录,对当前记录加锁
– select…lock in share mode , select…for update
– update , delete , insert快照读:不加锁(在事务隔离不为serializable条件下才成立,在serializable下,由于是串行读,此时的快照读退化成当前读)的非阻塞读
– select(默认)
不同隔离级别下的情况:
- 在rc级别下,当前读和快照读读到的数据一致(均是修改后的)
- 在rr级别下,当前读是更新后的,快照读如果在其他事务修改之前读取该数据,即之后读到的也是之前的,反之也是更新后的
造成原因(快照生成时机的不同):
- READ-COMMITTED:一个事务下,RC 每条 dml 语句(增删改查)读数据都会创建一个新的 read view (快照),其DB_TRX_ID 每次读都 +1
- REPEATABLE-READ:一个事务下,RR 第一条 dml 会对已存在的 undo log 按read view的规则创建一个快照。同一个事务下连续的多个select,也是只能读到同一个快照,即第一个快照的内容。(快照的构建时机很重要,在其它事务修改前 / 后)
- 快照读
读写不冲突,读取数据不需要加锁
- InnoDB存储引擎在每行记录上存有三个字段 DB_TRX_ID , DB_ROLL_PTR , DB_ROW_ID字段,用于实现快照读
(1) DB_TRX_ID:最新一次修改本行记录的操作ID
(2) DB_ROLL_PTR:回滚指针,指向当前记录项的undo log信息
(3) DB_ROW_ID:标识插入的新的数据行的id,无论有没有主键,都会通过该字段唯一标识一条记录
- undo日志
为了回滚而记录的消息称之为撤销日志–undo log
- read view
– 决定当前事务能看到哪个版本的数据
– 可见性算法:将要修改的数据的DB_TRX_ID取出来,与当前活跃事务ID做对比,如果大于或者等于,就通过DB_ROLL_PTR取出undo日志中上一层的DB_TRX_ID,直到小于。保证当前可见数据是最稳定的版本。当前事务ID>所要修改记录里的DB_TRX_ID