锁是计算机协调多个进程或线程并发访问某一资源的机制,在数据库中,锁是为了保证并发访问下数据的一致性而采用的机制
全局锁对整个数据库实例加锁,使用全局锁后,整个数据库处于只读状态,数据库表的增删改(DML)、表结构的更改(DDL)会被阻塞
典型的应用场景是进行全库的逻辑备份时,需要对所有的表进行锁定,从而获得一致性视图,保证数据的完整性
全局锁的加锁和释放锁语句如下
flush tables with read lock
unlock tables
表级锁可以分为表锁、元数据锁和意向锁
共享锁(读锁)
当对表加共享锁时,不影响自身线程与其它线程对该表的读操作,但会禁止自身线程的写操作,并阻塞其它线程的写操作
多个线程可以对同一个表加共享锁,只有所有的共享锁都释放完毕,表才能够执行写操作
加锁
lock tables 表名 read
释放锁
unlock tables / 客户端断开连接
排他锁(写锁,独占锁)
当对表加独占锁时,不影响自身线程对该表的读和写操作,但会阻塞其它线程的读和写操作
排他锁同时只能被一个线程占用,当自身线程对该表加排他锁时,其它线程对该表加排他锁会被阻塞(加共享锁也会被阻塞)
加锁
lock tables 表名 write
释放锁
unlock tables / 客户端断开连接
MDL 在 MySQL5.5 中引入,由系统自动控制,无需显式使用,在访问一个表的时候会自动加上
MDL 的作用是维护表的元数据的一致性,避免 DML 与 DDL 的冲突
当对一个表做增删查改时,加 MDL 读锁,当对表结构做更改操作时,加 MDL 写锁
事务中的 MDL 锁,在语句执行时开始申请,但语句结束后并不会马上释放,而是等到这个事务提交后才释放
若事务A锁住表中的一行(写锁)。事务B锁住整个表(写锁),事务A既然锁住了某一行,其他事务就不可能修改这一行,这与事务B锁住整个表就能修改表中的任意一行形成了冲突,故这种情况下,表锁和行锁不能共存
也就是说,当事务B想要对表加上表共享锁(表排他锁)时,需要逐行判断是否有行锁,以及行锁类型,才能得知表锁能否成功加上,效率较低,为了解决这个问题,引入意向锁
事务A在申请行锁(读锁)之前,数据库会自动给该表加上意向共享锁,而申请行锁(写锁)之前,会自动给该表加上意向排他锁
通过如下语句可以为该行加上共享锁,同时也就为该表自动加上了意向共享锁
select ... lock in share mode
通过如下语句可以为该行加上排他锁,同时也就为该表自动加上了意向排他锁
insert、update、delete、select ... for update
当一个事务为某个表加上意向锁后,另外一个事务过来想为表加上表共享锁(表排他锁)时,无需对表逐行进行行锁情况判断,只需直接查看表的意向锁情况,以决定锁是否能加上
InnoDB 的数据基于索引组织,行级锁是对索引的索引项加锁来实现的,而并非对实际记录加锁
行锁可锁住单行记录,防止其它事务进行 update 和 delete ,在 RC 和 RR 隔离级别下都支持
InnoDB 实现了两种行锁
共享锁与共享锁之间不互斥,而共享锁与排他锁、排他锁与排他锁之间互斥
默认的加锁情况如下
SQL | 行锁类型 | 说明 |
---|---|---|
insert | 排他锁 | 自动加锁 |
update | 排他锁 | 自动加锁 |
delete | 排他锁 | 自动加锁 |
select | 无 | 默认 select 不加锁 |
select ... lock in share mode | 共享锁 | 通过 lock in share mode 加共享锁 |
select ... for update | 排他锁 | 通过 for update 加排他锁 |
间隙锁可锁住索引记录的间隙(不含索引记录),防止其它事务在这个间隙进行 insert 而产生幻读,在 RR 隔离级别下支持
临键锁是行锁和间隙锁的组合,可以同时锁住索引记录和该记录前面的间隙(Gap),在 RR 隔离级别下支持
悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java 中 synchronized和 ReentrantLock 等独占锁就是悲观锁思想的实现
与悲观锁相对应,乐观锁认为数据的变动不会太频繁。因此,它允许多个事务同时对数据进行变动。 那么怎么去负责多个事务顺序对数据进行修改呢?乐观锁通常是通过在表中增加一个版本(version)或时间戳(timestamp)来实现,其中,版本最为常用。事务在从数据库中取数据时,会将该数据的版本也取出来(v1),当事务对数据变动完毕想要将其更新到表中时,会将之前取出的版本(v1)与数据中最新的版本(v2)相对比,如果 v1 = v2,那么说明在数据变动期间,没有其他事务对数据进行修改,此时,就允许事务对表中的数据进行修改,并且修改时 version 会加 1,以此来表明数据已被变动。如果 v1 不等于 v2,说明数据变动期间,数据被其他事务改动了,此时不允许数据更新到表中,一般的处理办法是通知用户让其重新操作。不同于悲观锁,乐观锁是人为控制的