锁的简单概述
- InnoDB 存储引擎既支持行级锁,也支持表级锁,默认情况下使用行级锁。
- 所谓表级锁,它直接锁住的是一个表,开销小,加锁快,不会出现死锁的情况,锁定粒度大,发生锁冲突的概率更高,并发度最低。
- 所谓行级锁,它直接锁住的是一条记录,开销大,加锁慢,发生锁冲突的概率较低,并发度很高。
- 所谓页级锁,它是锁住的一个页面,它的开销介于表级锁和行级锁中间,也可能会出现死锁,锁定粒度也介于表级锁和行级锁中间,并发度也介于表级锁和行级锁中间。
- 行级锁更适合大量按照索引条件并发更新少量不同的数据,同时还有并发查询的应用
-
命令行方式查看引擎
-
看你的mysql现在已提供什么存储引擎:
-
mysql> show engines;
-
看你的mysql当前默认的存储引擎:
-
mysql> show variables like ‘%storage_engine%’;
-
你要看某个表用了什么引擎(在显示结果里参数engine后面的就表示该表当前用的存储引擎):
-
mysql> show create table 表名;
InnoDB行级锁
- 数据库实现事务隔离的方式,基本可以分为两种:
- 在操纵数据之前,先对其加锁,防止其他事务对数据进行修改。这就需要各个事务串行操作才可以实现。
- 不加任何锁,通过生成一系列特定请求时间点的一致性数据快照,并通过这个快照来提供一致性读取(数据多版本并发控制,也就是多版本数据库,一般简称为
MVCC 或者 MCC,它是 Multi Version Concurrency Control 的简写。关于MVCC详细内容见下)
- InnoDB有两种类型的行级锁,两种内部使用的意向锁;
- 共享锁(S):允许一个事务读一行数据时,阻止其他的事务读取相同数据的排他锁。
- 排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据的共享锁和排他锁。
- 意向共享锁(IS):事务打算给数据行加行共享锁。事务在给一个数据行加共享锁前必须先取得该表的IS锁。
- 意向排他锁(IX):事务打算给数据行加行排他锁。事务在给一个数据行加排他锁前必须先取得该表的IX锁。
- 悲观锁(抽象,不真实存在的锁)
- 乐观锁(抽象,不真实存在的锁)
- 共享锁,排他锁语句
- 语句 + lock in share mode
- 语句 + for update
- 4种锁的共存逻辑关系
锁模式 | 共享锁(S) | 排他锁(X) | 意向共享锁(IS) | 意向排他锁(IX) |
---|---|---|---|---|
共享锁(S) | 兼容 | 冲突 | 兼容 | 冲突 |
排他锁(X) | 冲突 | 冲突 | 冲突 | 冲突 |
意向共享锁(IS) | 兼容 | 冲突 | 兼容 | 兼容 |
意向排他锁(IX) | 冲突 | 冲突 | 兼容 | 兼容 |
事务的隔离级别
事务并发下由于隔离级别的不同,存在的4种问题
- 数据更新丢失:两个事务同时操作一条数据,一个事务因为异常导致数据更新丢失
- 脏读 :一个事务开始读取了某行数据,另外一个事务已经更新了此数据但没有能够及时提交。这是相当危险的,因为很可能所有的操作都被回滚
- 不可重复读:一个事务对同一行数据重复读取两次,但是却得到了不同的结果。例如,在两次读取的中途,有另外一个事务对该型数据进行了修改,并提交
- 幻读:事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据(SQL不一定一样)。这是因为在两次查询过程中有另外一个事务插入数据
简单来说:脏读就是可以看到其他事务未提交的数据(可能会回滚),幻读就是无缘由的增加或减少了数据,不可重复读是无缘由的更改了数据
4种隔离级别
- 未授权读取(未提交读 Read Uncommitted):READ-UNCOMMITTED | 0:存在脏读,不可重复读,幻读的问题。如果一个事务已经开始写数据,则另外一个数据则不会允许同时进行写操作,但允许其他事务读此行数据。隔离级别可以通过“排他写锁”实现
- 授权读取(已读提交 Read committed):READ-COMMITTED | 1:解决脏读的问题,存在不可重复读,幻读的问题。这个可以通过“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行
- 可重复读取(Repeatable Read):REPEATABLE-READ | 2:解决脏读,不可重复读的问题,存在幻读的问题,默认隔离级别。可通过“共享锁”,“排他锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务
- 序列化(Serializable):SERIALIZABLE | 3:解决脏读,不可重复读,幻读,可保证事务安全,但完全串行执行,性能最低。提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须要通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。
对应关系表
隔离级别 | 读数据一致性 | 脏读 | 不可重复读的问题 | 幻读 | |
---|---|---|---|---|---|
未提交读 | 最低级别,只能保证不读取物理上损坏的数据 | 是 | 是 | 是 | 直接操作磁盘 |
已读提交 | 语句级 | 否 | 是 | 是 | 查询磁盘,操作内存 |
可重复读取 | 事务隔离级别 | 否 | 否 | 是 | 查内存,写内存 |
序列化 | 最高级别,事务级 | 否 | 否 | 否 |
事务的隔离级别设置的越高,并发的事务越少,越接近于串行
设置隔离级别 :SET @@gloabl.tx_isolation = ‘SERIALIZABLE’;
MVCC详述
基本特征
- 每行数据都存在一个版本,每次数据更新时都更新该版本
- 修改时Copy出当前版本随意修改,各个事务之间无干扰
- 保存时比较版本号,如果成功(commit),则覆盖原记录;失败则放弃copy(rollback)
InnoDB存储引擎MVCC的实现策略
在每一行数据中额外保存两个隐藏的列:当前行创建时的版本号和删除时的版本号,在进行update,delete操作是对两隐藏列进行操作,版本号自动+1。
id | name | c_version | d_version |
---|---|---|---|
1 | ghost | 1 |
进行update之后,旧的记录视为已删除,将新记录删除版本号
id | name | c_version | d_version |
---|---|---|---|
1 | ghost | 1 | 2 |
1 | ghostzzz | 2 |
删除,将新的版本号作为删除版本号
id | name | c_version | d_version |
---|---|---|---|
1 | ghostzzz | 2 | 3 |
通过上例可以看出:
- 删除版本号大于当前版本号或未指定,则当前记录未被删除
- 创建版本号 小于或者等于 当前事务版本号 ,就是说记录创建是在当前事务中(等于的情况)或者在当前事务启动之前的其他事物进行的insert
额外补充
MVCC的版本号只会在事务提交之后才会产生,所以支持的事务包括读已提交(Read committed)和可重复读(Repeatable Read)