-
数据库锁
- 对数据的操作类型划分:(意向读、写锁属于表级别锁,读、写锁主要针对的是行级锁)
- 读锁:读锁也叫共享锁S
- 自己事务读写:允许读不允许写
- 多个事务读写:允许同时读操作,但是不可以进行写操作
- 多个事务加锁:其他事务可以追加S锁,但不能追加X锁,需要等待锁释放
- 指令:select ... lock in share mode
- 写锁:写锁也叫排他锁X
- 自己事务读写:允许读写操作
- 多个事务读写情:既不允许读操作,也不允许写操作
- 多个事务加锁:其他事务不可以追加任何锁
- 指令:select ... for update
- 注意:行级锁的实现其实是依靠其对应的索引,所以如果操作没用到索引的查询,那么会锁住全表记录
- 意向读锁:IS:在对表记录加读锁之前会先获取表的IS锁,兼容行级锁会和表级锁发生冲突
- 意向写锁:IX:在对表记录加写锁之前会先获取表的IX锁,兼容行级锁会和表级锁发生冲突
- 读锁:读锁也叫共享锁S
- 锁的粒度:锁会占用空间如果占用空间超过阈值那么锁会升级为更大粒度的锁,会降低并发度
- 页锁:会出现死锁、会浪费数据加载一页数据却使用几行、资源开销介于行锁和表锁之间、锁的粒度也介于行锁和表锁直接
- 表锁:每次操作会对整张表锁定,并发粒度最低
- 1.意向锁:意向共享、意向排他锁:由存储引擎维护,无法手动操作在加共享/排他锁之前会先获取该行所在数据表对应的意向锁
- 2.读锁:lock tables 表名称 read;
- 注意:当前连接和其他连接可以读操作,当前连接写操作报错、其他连接写操作会阻塞
- 3.写锁:lock tables 表名称 write;
- 注意:当前连接可以读写操作,其他连接读写操作都会被阻塞
- 4.自增锁:主要针对自增主键,可以设置自增方式:简单插入、批量插入、混合模式插入,
- 注意:为了保证插入的连续性和唯一性,简单模式由于预先知道执行数量所以每次插入都会
- 指令:
- lock tables 表名称 read|write,表名称2 read|write; 该指令一次可以锁多张表,但是只能锁最近执行的表
- show open tables where In_use > 0;查看表上加过的锁
- unlock tables;
- 行锁:MySQL在REPEATABLE READ隔离级别下是可以解决幻读问题的,解决方案有两种,可以使用 MVCC 方案解决,也可以采用 加锁方案解决但是在使用加锁方案解决时有个大问题,就是事务在第一次执行读取操作时,那些幻影记录尚不存在,我们无法给这些 幻影记录 加上 记录锁 。InnoDB提出了一种称之为 Gap Locks 的锁,官方的类型名称为: LOCK_GAP ,我们可以简称为 gap锁
- 1.记录锁:S型记录锁 和 X型记录锁即共享行锁和排他行锁
- 2.间隙锁:不允许别的事务在锁定区间内间隙插入新记录
- 3.临键锁:既锁住当前记录还锁住间隙
- select * from student where id <=8 and id > 3 for update;
- 4.插入意向锁:在已锁的间隙内想插入数据由,需要阻塞等待,并且需要在内存中生成一个锁结构当前锁即为插入意向锁,但它不等同意向锁
- 对待锁的状态:
- 乐观锁:一般是通过代码层面使用表中版本字段在操作具体数据时带上之前版本作为条件
- 悲观锁:操作具体数据之前先获取锁将数据锁定,修改完再释放,共享锁和排他锁都是悲观锁只不过对锁的实现不同
- 行锁、表锁、读锁、写锁、共享锁、排他锁都是悲观锁的实现
- 加锁方式:
- 显式锁:通过特定的语句进行加锁,称之为显示加锁
- select .... lock in share mode
- select .... for update
- 隐式锁:
- 情景一:对于聚簇索引记录来说,有一个 trx_id 隐藏列,该隐藏列记录着最后改动该记录的 事务 id 。那么如果在当前事务中新插入一条聚簇索引记录后,该记录的 trx_id 隐藏列代表的的就是当前事务的 事务id ,如果其他事务此时想对该记录添加 S锁 或者 X锁 时,首先会看一下该记录的trx_id 隐藏列代表的事务是否是当前的活跃事务,如果是的话,那么就帮助当前事务创建一个 X锁 (也就是为当前事务创建一个锁结构, is_waiting 属性是 false ),然后自己进入等待状态(也就是为自己也创建一个锁结构, is_waiting 属性是 true )。
- 情景二:对于二级索引记录来说,本身并没有 trx_id 隐藏列,但是在二级索引页面的 Page Header 部分有一个 PAGE_MAX_TRX_ID 属性,该属性代表对该页面做改动的最大的 事务id ,如果PAGE_MAX_TRX_ID 属性值小于当前最小的活跃 事务id ,那么说明对该页面做修改的事务都已经提交了,否则就需要在页面中定位到对应的二级索引记录,然后回表找到它对应的聚簇索引记录,然后再重复 情景一 的做法。
- 执行下述语句,输出结果:
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert INTO student VALUES(34,"周八","二班"); Query OK, 1 row affected (0.00 sec) mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from student lock in share mode; - #执行完,当前事务被阻塞隐式锁的逻辑过程如下:
- A. InnoDB的每条记录中都一个隐含的trx_id字段,这个字段存在于聚簇索引的B+Tree中。
- B. 在操作一条记录前,首先根据记录中的trx_id检查该事务是否是活动的事务(未提交或回滚)。如果是活动的事务,首先将 隐式锁 转换为 显式锁 (就是为该事务添加一个锁)。
- C. 检查是否有锁冲突,如果有冲突,创建锁,并设置为waiting状态。如果没有冲突不加锁,跳到E
- D. 等待加锁成功,被唤醒,或者超时。
- E. 写数据,并将自己的trx_id写入trx_id字段。
- 显式锁:通过特定的语句进行加锁,称之为显示加锁
- 对数据的操作类型划分:(意向读、写锁属于表级别锁,读、写锁主要针对的是行级锁)
- 其他锁
- 全局锁:全局锁就是对 整个数据库实例 加锁。当你需要让整个库处于 只读状态 的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。全局锁的典型使用 场景 是:做 全库逻辑备份
- Flush tables with read lock
- 死锁:
- 死锁主要是由于程序逻辑导致的,A事务访问A表同时对A表加锁,再访问B表尝试获取锁。B事务访问B表同时对B表加锁,再访问A表这时候发现A表有锁然后就开始阻塞,A事务获取B表锁的时候发现获取不到开始阻塞,导致双方都在等待对方释放锁资源,最终发生死锁。MySQL解决了死锁是通过看那个事务更消耗资源直接报错释放锁
- 全局锁:全局锁就是对 整个数据库实例 加锁。当你需要让整个库处于 只读状态 的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句。全局锁的典型使用 场景 是:做 全库逻辑备份