MVCC机制遗留的问题
为什么在可重复读级别下,幻读没有产生?
回想一下在事务隔离级别那篇文章中,可串行化是通过什么保证的?
对操作的每一行记录加读锁、写锁和范围锁;任何其他事务都必须等待持有锁的事务释放锁之后才能进行操作;
而可重复读级别相比之下唯一少的就是范围锁,所以无论你是否了解过具体原因,都应该去猜测推理,大概率是加了范围锁。而在这里,他有一个特殊的名字,叫做间隙锁。
虽然我很想直接上间隙锁相关的内容,但是为了更加有体系化,最好还是完整梳理一下;
本篇文章最好是有一点基础再看,因为本身就是自记录,没有打算写一篇完整的教学博客。
读锁和写锁(共享锁和排它锁)
Shared Lock 共享锁(S锁),也叫读锁;不和读锁冲突,但和写锁冲突;
当事务A持有读锁的时候,事务B依然可以加读锁;但是除了事务A自己可以加写锁,其他事务都无法对这条记录加写锁。
Exclusive Lock 排他锁(X锁),也叫写锁;和谁都冲突;
即当事务A持有记录的写锁时,其他事务读锁和写锁都加不了
S | X | |
---|---|---|
S | 兼容 | 冲突 |
X | 冲突 | 冲突 |
行和列代表不同事务
表锁
上锁和解锁
lock tables 表名 [as alisa] 锁类型;
unlock tables ;
表锁的命令就是上述两行,且表锁也分读写锁,表级读写的兼容冲突和读写锁一致。
通过lock tables 命令加锁的session,在释放锁之前,能且只能执行lock tables 命令后面指定的表,命令类型和锁类型保持一致;比如 lock tables A read,那么后面就只能读A表,而不能执行读B表,或者写A表;如下面的例子一样;另外如果使用了别名,那么需要确保查询语句涉及的别名和lock table的别名完全一致;
lock tables simple read;
select * from simple;
select * from batch_insert;
//[HY000][1100] Table 'batch_insert' was not locked with LOCK TABLES
update simple set name=3 where id=2;
Table 'simple' was locked with a READ lock and can't be updated
Unlock tables 会显式的释放所有该session之前加的所有表;另一个作用是释放FLUSH TABLES WITH READ LOCK命令所加的全局读锁;
Another use for UNLOCK TABLES is to release the global read lock acquired with the FLUSH TABLES WITH READ LOCK statement, which enables you to lock all tables in all databases. See Section 13.7.8.3, “FLUSH Statement”.
lock tables、start transcation命令可以隐式的释放之前持有的锁;
查看锁情况
可通过下面的命令查看表是否上锁,name_locked为0表示上锁
show OPEN TABLES where In_use > 0;
WRITE locks normally have higher priority than READ locks to ensure that updates are processed as soon as possible. This means that if one session obtains a READ lock and then another session requests a WRITE lock, subsequent READ lock requests wait until the session that requested the WRITE lock has obtained the lock and released it.
对于读-写-读的情况,由于锁的优先级较高,如果申请写的session迟迟获取不到锁,会阻塞后续其他session申请读锁;具体分析看Case1;
全局读锁
关于全局锁,我一共只在两篇文档中看到过;一个是《Mysql45讲》的06篇,一个mysql官方文档的lock-table文章和FLUSH Statement文章,所以了解的并不全,加上此时的我还不太关心数据库主从的问题,所以也没有深入研究。
FLUSH TABLES WITH READ LOCK
Closes all open tables and locks all tables for all databases with a global read lock.
元数据锁
Statements acquire metadata locks one by one, not simultaneously, and perform deadlock detection in the process.
DML statements normally acquire locks in the order in which tables are mentioned in the statement.
DDL statements, LOCK TABLES, and other similar statements try to reduce the number of possible deadlocks between concurrent DDL statements by acquiring locks on explicitly named tables in name order.
元数据锁是一个个获取的,DML和DDL通过不同的方式定义执行的顺序;官网提供了一个rename table的顺序例子,但那个例子挺迷的;
//可以通过这个表查看元数据锁的情况
select * from performance_schema.metadata_locks;
To ensure transaction serializability, the server must not permit one session to perform a data definition language (DDL) statement on a table that is used in an uncompleted explicitly or implicitly started transaction in another session. The server achieves this by acquiring metadata locks on tables used within a transaction and deferring release of those locks until the transaction ends. A metadata lock on a table prevents changes to the table's structure. This locking approach has the implication that a table that is being used by a transaction within one session cannot be used in DDL statements by other sessions until the transaction ends.
如果一个session或者一个事务持有某个表的元数据锁,那么另一个session或者事务就无法执行DDL操作;
https://dev.mysql.com/doc/refman/8.0/en/metadata-locking.html
读写阻塞问题
关于元数据锁,在《Mysql45讲》中有提到一个问题,后加的读锁会被前面的写锁所阻塞,很类似于表锁最后提到的优先级问题,有没有可能是一个原因呢?具体见case2
行锁(Record Lock)
A record lock is a lock on an index record.
行锁是在索引上的一个锁。这句话非常重要!
这里的索引可以是聚簇索引也可以是二级索引,如果表中没有索引或者查询的条件没有索引,又或者优化器认为索引没有作用,这个时候就会退化为“表锁”,但我总感觉像是锁定了所有行。
另外,如果表中没有定义聚簇索引,会自动生成一个隐藏的索引。
间隙锁(Gap Lock)
A gap lock is a lock on a gap between index records, or a lock on the gap before the first or after the last index record.
单靠行锁是无法解决幻读的问题的,所以innodb引入了间隙锁的概念,只在RR级别生效。间隙锁是一个范围锁,比如所以索引1和索引3之间就存在(1,3)这样一个间隙,当这个间隙被锁定的时候,就无法插入值为2的记录。
不同的事务对于同一个间隙加锁是允许发生的,因为都是在保护这个间隙不被插入数据。
Gap locking is not needed for statements that lock rows using a unique index to search for a unique row.
当查询条件是唯一索引,如果查询的值存在且是唯一的一行记录,那么是不需要加间隙锁的;因为间隙锁的出现就是为了防止幻读,对于加了唯一索引的表,同样的查询条件永远只能查出唯一的一条,既然已经保证了唯一,那么就没有间隙锁的必要了。
那如果查询结果不存在?以及查询条件是范围查询?又或者是普通索引甚至没有索引呢?
关于这些情况的排列组合,见case3..
临键锁(next-key Lock)
A next-key lock is a combination of a record lock on the index record and a gap lock on the gap before the index record.
临建锁=行锁+间隙锁,是innodb RR级别默认加的锁;由于锁定的是当前索引记录行和索引前的部分,所以一般总结为左开右闭;
假如存在索引10,11,13,20,那么就会存在以下几个区间,最后一个范围是mysql会假定一个非常大的supremum,但由于实际并不存在这个值,所以是左开右开。
(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)
意向锁
表意向锁
innodb支持多粒度锁,即允许行锁和表锁同时存在,并且在加锁的时候需要进行冲突检测;
比如事务A已经持有了a表的一条记录索引的行锁,这个时候B事务想要给a表加表锁,就需要一行行查看是否存在行锁;为了优化这种情况,innodb引入了意向锁的概念。
表意向锁是个表级锁,分为读意向锁(IS)和写意向锁(IX),它们添加的时机是在对行索引添加S锁和X锁之前;即如果想要对某一行加锁,就必须先取得这个表的意向锁。这样当另一个事务需要判断时,就不需要一行行进行检查,只需要查看这个表是否具有意向锁即可。
意向锁的作用主要是用来阻塞表锁的。所以其互相之间是不存在互斥的,只和表锁存在冲突,即读写冲突,具体就像是下面表格这样;
暂时无法在飞书文档外展示此内容
插入意向锁
An insert intention lock is a type of gap lock set by INSERT operations prior to row insertion. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap. Suppose that there are index records with values of 4 and 7. Separate transactions that attempt to insert values of 5 and 6, respectively, each lock the gap between 4 and 7 with insert intention locks prior to obtaining the exclusive lock on the inserted row, but do not block each other because the rows are nonconflicting.
插入意向锁是间隙锁类型的一种意向锁,锁的是间隙;是在进行插入之前必须申请获得的锁,所以和间隙锁是冲突的;换句话说,如果你想插入一条语句,那么这个语句对应的间隙必须不存在锁,这样你才能加上插入意向锁,进而插入数据;
而且,插入意向锁只要插入的不是同一行,那么就可以同时插入;
自增锁(AUTO-INC Locks)
An AUTO-INC lock is a special table-level lock taken by transactions inserting into tables with AUTO_INCREMENT columns.
如官方文档所说,自增锁其实是只针对于自增的字段,算是一个表级锁,一般对我们来说就是自增主键;当有多个事务同时想要插入,由于自增的值必须保持连续,所以多个事务的插入必须串行;
参考文档:
https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html#innodb-shared-exclusive-locks
https://dev.mysql.com/doc/refman/8.0/en/lock-tables.html
06 | 全局锁和表锁 :给表加个字段怎么有这么多阻碍?-极客时间
mysql MDL读写锁阻塞,以及online ddl造成的“插队”现象_花落的速度的博客-CSDN博客
MYSQL查看表是否被锁、以及解锁_mysql查看锁表_清石小猿的博客-CSDN博客