在并发事务中读-读
并不会引起什么问题。对于写-写
、读-写
、写-读
可能会引起一些问题,需要使用mvcc或加锁解决。由于既要允许读-读
情况不受影响,所以mysql实现了一个由两种类型的锁组成的锁系统来解决。通常被称为共享锁(Share Lock)
和排他锁(Exclusive Lock)
,也叫读锁和写锁。
(1) 对记录加S锁
SELECT ... LOCK IN SHARE MODE;
或者
SELECT ... FOR SHARE;(8.0)
(2) 对记录加X锁
SELECT ... FOR UPDATE;(8.0)
在mysql5.7及之前版本,如果获取不到锁,会一直等待,直到innodb_lock_wait_timeout
超时。在8.0中,如果后面加nowait、skip locked可以跳过等待,或者只返回没锁定的行。
锁定的数据范围越小,往往系统需要耗费更高的资源。所以数据库系统需要在高并发响应
和系统性能
两方面进行平衡,这样就产生了锁粒度
的概念。
该锁会锁定整张表,并不依赖存储引擎。表锁可以避免死锁问题,但是并发率大打折扣。
表锁分为:表级别的s锁、x锁
、意向锁
、自增锁
、元数据锁
一般情况下,不会使用innodb存储引擎提供的表级s锁和x锁。在一些特殊情况下,比如崩溃恢复过程中用到。在系统遍历autocommit = 0,innodb_table_locks=1
时,手动获取表锁方式未:
LOCK TABLES t READ
LOCK TABLES t WRITE
unlock tables : 解锁当前加锁的表
show open tables : 查看表当前是否加锁
意向锁是一种表锁,它的存在是为了协调行锁和表锁关系的,它不与行锁冲突,表明某个事务正在某些行持有了锁。
意向锁分为意向共享锁(IS)和意向排他锁(IX)。
如果我们给某一行数据加上了排他锁,数据库会自动给更大一级的空间加上意向锁。
所有插入数据的方式总共分三类,分别是:
在插入时,mysql采用自增锁的方式来实现。当向使用auto_increment列插入数据时需要获取一种特殊的表级锁,在插入语句时加一个自增锁。然后再语句执行后,再把自增锁释放掉。一个事务再持有锁时,其他事务的插入语句都要被阻塞,所以并发性并不高。所以innodb通过innodb_autoinc_lock_mode
的不同取值来提供不同的锁定机制。
当对一个表做增删改查操作的时候,加MDL读锁;当要对表的结构变更时,加MDL写锁。
用来解决DML和DDL操作之间一致性问题,不需要显式使用。
行锁也成为记录锁,mysql服务器层并没有实现行锁机制,行锁只在存储引擎层实现。
InnoDB与MyISAM的最大不同有两点:一是支持事务,二是采用了行级锁。
行锁分为:记录锁
、间隙锁
、临键锁
和插入意向锁
。
官方的类型名称为:LOCK_REC_NOT_GAP
,用来锁住一条记录的。
记录锁分为S型记录锁
和X型记录锁
。
MYSQL在RR隔离级别下是可以解决幻读的。解决方案有两种,第一种是MVCC,第二种是加间隙锁。对一条记录加了gap锁,并不会限制其他事务对这条记录加记录锁或者gap锁。
临键锁可以理解为一种特殊的间隙锁,上面说过了通过临建锁可以解决幻读的问题。 每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。
在插入一条记录时,如果插入位置被别的事务加了gap锁,那么就需要等待。在等待时,innodb规定必须再内存中生成一个锁结构,表明有事务再等待。把这种类型的锁命名为Insert intention locaks
。
插入意向锁是一种特殊的间隙锁。
页锁就是在页的粒度上进行锁定,锁定的数据资源比行锁多,开销介于表锁和行锁之间,会出现死锁。
每个层级的锁数量是有限制的,应为锁会占用空间,锁空间的大小是有限的。当某个层级的锁数量超过了这个层级的阈值时,就会进行锁升级。锁升级就是用更大粒度的锁替代多个更小粒度的锁。
悲观锁总是假设最坏的情况,每次去拿数据都认为别人会修改,所以每次在拿数据的时候都会上锁。(每次只有一个线程使用,其他线程阻塞)。比如行锁、表锁都是在操作前就上锁,其他资源都需要阻塞。
java中的synchronized和reentrantlock等独占锁就是悲观锁思想的实现。
注意: select … fro update语句在执行过程中所有扫描的行都会被锁上,因此在mysql中用悲观锁必须确定使用了索引,而不是全表扫描,否则将会把整个表锁住。
乐观锁认为对同一数据的并发操作不会总发生,不用每次都上锁。在更新的时候判断有没有人去更新这个数据即可。也就是不采用数据库自身的锁机制,而是通过程序来实现。在java中juc.atomic包下的原子变量类就是使用了乐观锁的一种实现方式:cas实现的。
即一个事务对新插入的记录可以不显示的加锁。但是由于事务id的存在,相当于加了一个隐式锁。别的事务在对这条记录加锁时,会帮当前事务生成一个锁结构,从而减少锁的数量。
全局锁就是对整个数据库加锁。当你需要让整个库处于只读状态的时候,可以使用这个命令。典型的使用场景是:做全库逻辑备份。
Flush tables with read lock
死锁就是两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环。处理方式有两种:
一个锁的本质就是在内存中创建一个锁结构与之相关,符合下边条件的记录会被放到一个锁结构中。
innodb存储引擎中的锁结构如下:
show status like 'innodb_row_lock%'
mysql中把事务和锁的信息记录在了information_schema
库中,涉及到的三张表为:innodb_trx、innodb_locks和innodb_lock_waits。