为什么要了解 MyISAM 与 InnoDB 锁方面的区别
1. InnoDB 默认支持行级锁,而 MyISAM 默认支持表级锁
2. 表级锁会锁表
1) 即当对数据进行 select 操作对时候,它会自动为表加一个表级的读锁;
2) 当对数据增删改的时候,它会自动为我们加一个表级别的写锁;
当一个表的读锁未被释放时,另一个 session 想要对同一张表加写锁时就会被阻塞,直到所有对读锁被释放;
反之先写后读同样会被阻塞
3. 行级锁并不会锁表,即使是针对同一行数据,增删改操作也不会阻塞读操作
4. 如果不了解 InnoDB 关于锁方面的原理,某些操作仍然会导致锁表,造成性能急剧下降
InnoDB 的锁演示
1. 为了演示 InnoDB 锁的原理,首先关闭自动提交
InnoDB 默认为自动提交,即在执行一个 SQL 的时候,它会自动提交,我们可以通过 show variables like "autocommit";
查看是否为自动提交.
2. 为了演示 InnoDB 锁的原理,首先关闭自动提交set autocommit = off;
【该命令只能关闭当前会话的】【当然,我们可以不用改配置,在执行相应SQL之前,使用begin transaction
开启事务即可,在需要提交的时候,再commit】
自动提交本质上是 Innodb 使用了二段锁,加锁和解锁是分为两个步骤进行的,即先对同一个事务里面的一批操作分别进行加锁,然后到 commit 的时候,再对事务里面加的锁统一解锁
非阻塞读演示【读操作不会阻塞增删改操作】
1. 整体表结构如下
2. 首先在会话1中读取一下"person_info_large"表id=3的数据,但不提交
3. 在另一个会话中同样对 id=3 对数据进行修改操作,也不提交【从结果可以看出,对同一行对读操作并没有阻塞当前更新操作】【是因为 InnoDB 的 select 并没有对该行上锁,即是 InnoDB 的非阻塞 select特性】
读上锁演示【读操作上锁会阻塞增删该操作】
1. 在一个会话中读取id =3 的数据时,加读锁;使用【lock in share mode】
2. 在另一个会话中,修改该行数据,【会被阻塞】,即只有第一个会话提交后,第二个会话中才能获得锁
InnoDB 支持行级锁演示 【同行的修改操作会相互阻塞,不同行的修改不会相互阻塞【上锁的读在同一行操作时,也会相互阻塞】】
1. 先演示同行的修改操作会相互阻塞,两个会话同时修改ID=4的数据
1) 会话1的操作
2) 会话2的操作会被阻塞
2. 不同行的修改操作不会阻塞
1)会话 1 的操作
2)会话2的操作【可以看到,会话2的修改不同行的操作时,并不会被阻塞,因此可以得出 InnoDB 可以支持行级锁】
即 InnoDB 默认支持行级锁;并且 select、update 同一行数据的时候,select 并不阻塞 update 操作,update 操作也不阻塞 select 操作
从上面到演示中可以看到,检索条件都使用的是主键索引,那行级锁是否和索引有关呢?
这里先给出结论,后面做演示:
1. 除主键索引以外的其他键,如唯一索引、普通索引,只要 SQL 用到索引,涉及的行都会被上共享锁和排它锁
2. InnoDB 当不走索引的时候,整张表就会被锁住,也就是说此时用的是表级锁;因此 InnoDB 在 SQL 没有用到索引的时候,用的是表级锁,而 SQL 用到索引的时候,用的是行级锁以及 gap 锁【gap 锁在走普通非唯一索引时使用,后面会有单独文章分析】
3. InnoDB 除支持行级锁外,还支持表级的意向锁,意向锁也分为共享读锁(IS),还有排它写锁(IX),和myisam的表锁差不多。主要是为了表级别的操作的时候不用轮询每一行看看有没有上行锁。
演示
从下图中可以看出,name 字段没有索引,我们就以name字段进行演示
我们就使用id =1和id=2的name进行演示,两个name不同
1)会话 1 更新id=1 的行数据
-
会话2 更新id = 2 的行数据【此时,我们本身更新的是不同行的数据,但是会话2仍然被阻塞了,即 InnoDB 在不走索引的时候,整张表都会被锁住,】
innodb默认是行级锁,也支持表级锁,而myisam默认支持表级锁,但不支持行级锁;无论是表级锁还是行级锁,都分为共享锁和排它锁。
MyISAM 的锁
关于 MyISAM 的锁就不演示了,现阶段日常开发我们也不会使用 MyISAM 引擎,这里仅介绍其特性
- MyISAM默认用的是表级锁,不支持行级锁。
- 读操作和写操作相互之间会阻塞
- 写操作之间也会阻塞
- 读操作之间不会阻塞
共享锁和排它锁的兼容性
X 排它锁;增删改操作会上排他锁
S 共享锁
\ | X | S |
---|---|---|
X | 冲突 | 冲突 |
S | 冲突 | 冲突 |
增删改或者select for update 都会上排它锁。如果一个session对一行记录上了排它锁,则另一个session对这行记录上排它锁时,会冲突,必须等第一个session释放了锁,才能上锁。而如果另一个session对这行记录上共享锁,也是冲突的。如果一个session对一行记录上共享锁,另一个session对这行记录上上排它锁是,会冲突,必须等第一个session释放了锁,才能上锁。而如果另一个session对这行记录上共享锁,此时是不冲突的。
MyISAM 和 InnoDB 分别适用的场景
MyISAM
- 频繁执行全表count语句。【因为MYISAM用一个变量保存了整个表的行数,而Innodb执行该语句时需要从新扫描表进行统计】
- 对数据进行增删改的频率不高,查询非常频繁
- 没有事务
InnoDB
- 数据增删改查都相当频繁。【增删改的时候,只是某些行被锁,早大多数情况下避免了阻塞,而不像myisam对某行的增删改,都会锁住整张表】
- 可靠性要求比较高,要求支持事务
行级锁是不是一定比表级锁要好?
未必,锁的粒度越细,代价越高,相比表级锁在表的头部直接加锁,行级锁还需要扫描
某行的时候对齐上锁,这样代价比较大,Innodb支持事务的同时,也相比MyISAM引擎带来了更大的开销,同时,在之前的索引部分也了解到,Innodb 必须有且仅有一个聚集索引,数据文件是和索引绑在一起的,必须要有主键,通过主键索引效率很高,但是辅助索引需要查两次,先查到主键,再通过主键查询到数据,而MyISAM 是非聚集索引,数据和文件是分离的,索引保存的时候数据文件的指针,主键索引和辅助索引是独立的,因此 MyISAM 引擎在纯检索系统中【增删改很少的系统中】其性能要好于Innodb。
数据库锁的分类
按锁粒度划分
可分为表级锁、行级锁、页级锁。innodb默认支持行级锁,同时也支持表级锁,innodb对行级上锁的时候,会先上一种表级别的意向锁。】【myisam仅支持表级锁。】
按锁级别划分
可分为共享锁、排它锁
按加锁方式划分
可分为自动锁、显式锁【像意向锁、还有myisam的表锁、以及update、insert、delete的时候,加上的锁就是自动锁,因为这是mysql自动为我们上的。而select for update 、lock share mode 这些我们显式加的锁就是显式锁了】
按操作划分
可分为DML锁、DDL锁【对数据进行操作上的锁就称为DML锁(包括对数据的增删改查),而对表结构进行变更的,如alter table这些语句,加上的锁就是DDL锁】
按使用方式划分
可分为乐观锁、悲观锁。
1. 悲观锁
悲观锁是先取锁再访问的策略,为数据处理的安全提供了保证,但是在效率方面,处理加锁的机制,会让数据库产生额外的开销,还有增加产生死锁的机会,另外在只读型事务处理中,由于不会产生冲突,也没有必要使用锁。如果上锁,会增加系统负担,还会降低并行性,如果一个事务锁定了某行数据,其他事务就必须等待该事务处理完,才可以去处理
多session的互锁操作,就是悲观锁的实现
2. 乐观锁
而乐观锁认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会对数据的冲突与否进行检测,如果冲突了,则返回用户错误的信息,让用户决定如何去做。相对于悲观锁,乐观锁并不会使用数据库提供的锁机制【一般乐观锁的实现是记录数据的版本,实现版本有两种方式,第一个是使用版本号,第二种是使用时间戳】
使用版本号是基于数据版本的,即version记录机制的实现方式。
何为数据版本,即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的version字段实现。
即我们对数据每更新一次,该version字段的值就加1,当我们提交更新的时候,去判断数据库表对应记录的当前版本信息,与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据