文章目录
一、锁机制基本概念
数据库是一个多用户使用的共享资源,当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况,若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。
加锁是实现数据库并发控制的一个非常重要的技术,当事务在某个数据对象操作前,先向系统发出请求,对其加锁。加锁后事务就对该数据对象有了一定的控制,在该事务释放锁之前,其他事务不能对此数据进行操作。
二、锁的分类
(一) 读锁(S共享锁)
读锁又称为共享锁,当事务对数据对象加上读锁后,允许其他事务添加读锁,但是不能添加写锁,添加写锁会阻塞。
给表test添加读锁:
lock table test read;
释放锁:
unlock tables;
(二) 写锁(X排他锁)
写锁又称为排他锁,若事务对数据对象加上写锁,不允许其他事务添加任何所,操作会阻塞。
给表test添加写锁:
lock table test write;
(三) 表锁
表锁会给一张表加锁,锁住整张表,粒度大,可以选择给表加读锁或写锁。加锁时,会给整张表加锁,其他行也不能被操作。MyISAM和InnoDB存储引擎都支持表锁,MyISAM默认使用表锁。
如给test表加读锁:
lock table test read;//读锁,写锁就是write
其他用户可以查询表,但是如果有其他用户进行修改,那么就会一直阻塞,因为此时test表被加锁,只有解锁后,其他用户的执行才可以正常执行。
(四) 行锁
行锁给一行数据加锁,粒度小,包含读锁,写锁。
给一行加读锁:
SQL语句选择一行 lock in share mode;
select * from test where id=1 in share mode;
给一行数据加写锁:
SQL语句选择一行 for update;
如:
select * from test where id=1 for update;//给此行加写锁
加锁只给一行数据加锁,其他行可以被操作,InnoDB默认支持行锁。
(五) 乐观锁
每次获取数据的时候,都不会担心数据被修改,所以每次获取数据的时候都不会进行加锁,但是在更新数据的时候需要判断该数据是否被别人修改过,可以通过版本标识实现,自身数据有一个版本标识,数据更新一次,版本标识自动+1,标识不一样代表匹配失败,数据更新了:
- 如果标识匹配失败,表示被修改过,则不进行数据更新。
- 如果标识匹配,表示数据没有被其他事务修改,则进行数据更新,
特点:
- 因为数据没有进行加锁,期间该数据可以被其他事务进行读写操作。
- 适合读取操作比较频繁的场景。
- 不适合大量的写入操作,因为数据发生冲突的可能性增大。
- 为了保证数据的一致性,应用层需要不断重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。
(六) 悲观锁
认为每个操作都会出现错误,所以每个操作之前都提前加锁,确保在自己使用的过程中,数据不会被别人修改,使用完成后进行数据解锁,其他事务阻塞的等待, 如:
- MyISAM:表锁(包含读锁,写锁)
- InnoDB:行锁(包含读锁,写锁)
特点:
- 适合写入操作比较频繁的场景。
- 如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。
(七) 意向锁
意向锁的目的是防止死锁出现,MyISAM表锁粒度大,不容易发生死锁。InnoDB行锁,粒度小,容易发生死锁。
意向锁就是,在加读锁,写锁之前,先加意向锁,意向锁可以共存。判断是否满足一个兼容规则:
- 加上意向锁后满足兼容规则,表示继续加锁不会出现死锁。
- 如果不满足兼容规则,表示继续加锁会出现死锁的情况,需要进行等待,等待兼容后,可以进行加锁。
(八) 间隙锁
间隙锁:给每次操作的间隙加上锁,将数据固定住,常用来解决幻读问题。如:
给第一次操作结果:4,5加上间隙锁后。再进行插入后,查询结果应该是4,5,6,但是因为4,5之间有间隙锁,所以不能将6插入结果集中,最后的结果只能从间隙锁中读,故还是4,5。
解决了幻读问题,幻读就是因为insert,delete操作导致一个连接,在一次事务中读到两次不一样的数据,将一次的数据锁住,那么第二次,第三次后面的操作不能影响最后的结果。
对于MySQL,隔离级别预告,加锁越严格,产生锁冲突可能性就越高,并发性事务处理性能降低。所以,在应用中,应该尽量使用中等的隔离级别,减少锁征用的几率。
三、MyISAM 表锁详解
MyISAM存储引擎支持表锁,加锁会给一张表加锁,所有行。有读锁,写锁两种锁:
- 读锁称为共享读锁。读-读。
- 写锁独占一张表,称为独占写锁。
底层系统自动SQL语句加锁,即在执行语句时,系统会自动先加一个锁,再开始运行,执行完毕解锁,属于悲观锁:
- select 加读锁;
- insert,delete,update加写锁。
【1. 给表加读锁:】
保证将当前表的存储引擎设置为MyISAM。
-
给A事务给test表手动加读锁:
-
B事务,可以进行select查询,但是不能进行修改操作,会阻塞,因为update修改操作,系统自动加写锁,此时表被A事务加了读锁,B事务加写锁,会阻塞,直到A事务释放读锁,才可以操作成功:
- A事务释放读锁,B事务立马加写锁成功,正常运行:
故:
- 读锁可以在同一张表上共存,即读锁共享。
- 不能加写锁,所以读锁和写锁不能共存。
将读锁称为共享读锁。
【2. 给表加写锁】
- A事务给test表加写锁,此时B事务的查询,修改都不能执行,都会阻塞:
- 只有A释放锁才可以正常执行:
故:
- 写-读互斥
- 写-写互斥
所以写锁独占一张表,所以写锁又称为独占写锁。
四、InnoDB 锁详解
InnoDBz支持行锁和表锁,默认行锁:
- 行锁:InnoDB必须有一个主键索引,叶子节点存放一行数据,给一行数据加行锁,数据在索引上,所以行锁加在了索引结构上。使用主键进行查询,就是索引查询,用到了索引结构,所以使用索引查询时可以加行锁。给一行数据加锁,不影响其他行,粒度小。
- 表锁:使用非索引查询,用不到索引结构,只能加表锁。
包含读锁,写锁:
- 读锁称为共享锁。
- 写锁称为排他锁。
底层系统给SQL语句自动加锁,存在超时机制,如果阻塞操作超时,就自动取消此次操作:
- select不加锁,因为SQL语句是一个事务,可以通过事务的隔离性和一致性保证事务并发执行。
- insert,delete,update加写锁
注意: InnoDB存储引擎支持事务,一条SQL语句就是一个事务,碰到分号;语句执行完成,事务结束,所有资源释放,所以当A事务执行加锁SQL语句后,分号结束事务结束,锁被释放。 为了看到行锁的效果,我们取消事务的自动提交功能:
set autocommit=0;
那么每次写完SQL语句后,事务不会提交到数据库,此时锁还存在,最后需要手动commit提交,事务才完成,资源才被释放。
(一)加行锁
只有主键索引可以加行锁,即查询中使用到了主键,才能加行锁,如果查询不是用的主键,加的锁称为表锁,A加锁后,B不能对整张表操作,如test表结构:
只有使用name进行查询时加的锁称为行锁,因为用到了索引结构,可以加行锁;但是如果查询使用salary字段,加的锁就是表锁。
取消自动提交事务,否则分号结束,锁被释放:
【1. 加读锁】
- A给通过主键name进行索引查询,可以加行锁,给第一行加读锁,share mode:
- B可以给第一行加读锁,可以成功,但是B事务修改第一行数据,底层加写锁,阻塞,如果超过一定时间,系统会取消此次操作,不会出现一直阻塞的情况。
超过一定时间,A事务没有释放释,那么系统取消B事务此次操作:
- 但是B事务可以修改其他行数据,如第二行等:
表示读锁-写锁不允许共存。
【2. 加写锁】
- A事务,利用主键索引查询,加的是行锁,给lisi行加锁,for update表示加写锁:
- B查询第一行,查询成功,因为select不加锁,所以可以读取数据。 B事务手动给同一行加读锁,阻塞,超过时间,系统取消此次操作:
- B修改其他行,可以执行,表示加的行锁:
故行级别加锁:
- 读锁-读锁共存,共享读锁
- 读锁-写锁不允许共存。
- 写锁-写锁不允许共存
- 写锁称为排他锁。(行级别)
(二)加表锁
根据非主键进行查询时,不会用到索引结构,无法给一行加锁,建立的锁只能是表锁。
- 如根据薪水进行查询时,不会用到索引,建立表锁,A建立了写锁,锁定了整张表:
- 此时B事务只能查询数据,不能对整张表中的任意行进行修改或加读锁、写锁,所以B不能对第二行操作,因为整张表都被锁了:
故表级加锁(和MyISAM一样)
- 读锁共享
- 写锁互斥,整张表被独占。
五、锁的粒度
锁具有粒度,可以对不同的资源进行加锁,资源大锁粒度大;资源小锁粒度小,粒度包含:行,页,表,库等资源。
- 锁定在较小的粒度的资源(如行锁)上可以增加系统的并发量,但需要较大的系统开销,因为锁的粒度较小,导致锁数量增加,容易发生死锁。
- 锁定在较大的粒度(例如表锁)并发难实现,如表锁,锁定了整张表,限制了其他事务对表中任意部分进行访问,但要求的开销较低,因为维护的锁少。
加油哦!🥓。