MySQL 学习笔记——锁

什么是锁

锁是计算机协调多个进程或线程并发访问某一资源的机制,在数据库中,锁是为了保证并发访问下数据的一致性而采用的机制


锁的分类

全局锁

全局锁对整个数据库实例加锁,使用全局锁后,整个数据库处于只读状态,数据库表的增删改(DML)、表结构的更改(DDL)会被阻塞

典型的应用场景是进行全库的逻辑备份时,需要对所有的表进行锁定,从而获得一致性视图,保证数据的完整性

全局锁的加锁和释放锁语句如下

flush tables with read lock
unlock tables

表级锁

表级锁可以分为表锁、元数据锁和意向锁

表锁

共享锁(读锁)

当对表加共享锁时,不影响自身线程与其它线程对该表的读操作,但会禁止自身线程的写操作,并阻塞其它线程的写操作

多个线程可以对同一个表加共享锁,只有所有的共享锁都释放完毕,表才能够执行写操作

加锁

lock tables 表名 read

释放锁

unlock tables / 客户端断开连接

排他锁(写锁,独占锁)

当对表加独占锁时,不影响自身线程对该表的读和写操作,但会阻塞其它线程的读和写操作

排他锁同时只能被一个线程占用,当自身线程对该表加排他锁时,其它线程对该表加排他锁会被阻塞(加共享锁也会被阻塞)

加锁

lock tables 表名 write

释放锁

unlock tables / 客户端断开连接

元数据锁(MDL,meta data lock)

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

当一个事务为某个表加上意向锁后,另外一个事务过来想为表加上表共享锁(表排他锁)时,无需对表逐行进行行锁情况判断,只需直接查看表的意向锁情况,以决定锁是否能加上

  • 意向共享锁(IS):与表共享锁不互斥,与表排他锁互斥
  • 意向排他锁(IX):与表共享锁和表排他锁均互斥
  • 意向锁之间不会互斥

行级锁

InnoDB 的数据基于索引组织,行级锁是对索引的索引项加锁来实现的,而并非对实际记录加锁

行锁(Record Lock)

行锁可锁住单行记录,防止其它事务进行 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 加排他锁

间隙锁(Gap Lock)

间隙锁可锁住索引记录的间隙(不含索引记录),防止其它事务在这个间隙进行 insert 而产生幻读,在 RR 隔离级别下支持

临键锁(Next-Key Lock)

临键锁是行锁和间隙锁的组合,可以同时锁住索引记录和该记录前面的间隙(Gap),在 RR 隔离级别下支持

悲观锁

悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java 中 synchronized和 ReentrantLock 等独占锁就是悲观锁思想的实现

乐观锁

与悲观锁相对应,乐观锁认为数据的变动不会太频繁。因此,它允许多个事务同时对数据进行变动。 那么怎么去负责多个事务顺序对数据进行修改呢?乐观锁通常是通过在表中增加一个版本(version)或时间戳(timestamp)来实现,其中,版本最为常用。事务在从数据库中取数据时,会将该数据的版本也取出来(v1),当事务对数据变动完毕想要将其更新到表中时,会将之前取出的版本(v1)与数据中最新的版本(v2)相对比,如果 v1 = v2,那么说明在数据变动期间,没有其他事务对数据进行修改,此时,就允许事务对表中的数据进行修改,并且修改时 version 会加 1,以此来表明数据已被变动。如果 v1 不等于 v2,说明数据变动期间,数据被其他事务改动了,此时不允许数据更新到表中,一般的处理办法是通知用户让其重新操作。不同于悲观锁,乐观锁是人为控制的

你可能感兴趣的:(MySQL,java,MySQL,锁)