锁,是为了线程能够有序的利用资源的一种解决手段,同时也是保持数据一致性的机制,InnoDB支持行级锁。数据库的锁机制,用于管理对共享资源的并发访问,InnoDB存储引擎会在行级别上对表数据上锁,但是也会在数据库内部其他地方使用锁。

InnoDB存储引擎中的锁

InnoDB存储引擎实现了两种类型的行级锁

⑴共享锁(S LOCK),允许事务读取一行数据;

⑵排它锁(X LOCK),允许事务删除或更新一行数据。

锁兼容:多个事务可以获取某行上的相同的锁,不需要等待;锁不兼容:事务只能等待其他事务释放锁,才可以获取锁,例如当事务获取某行的S锁,其它事务要想获取该行的X锁,则需要等待事务释放该行上的S锁。只要涉及X锁,不会和其他的锁兼容。InnoDB存储引擎支持独立度锁定,锁允许行级别锁和表级别锁同时存在。即意向锁,意向锁是表级别的锁,其设计目的是为了在一个事务中揭示下一行将被请求的锁的类型。

InnoDB存储引擎支持两种意向锁:

⑴意向共享锁(IS LOCK),事务获取一个表中某几行的共享锁;

⑵意向排他锁(IX LOCK),事务获取一个表中某几行的排他锁。

如果想观察锁的状态,可以使用SHOW ENGINE INNODB STATUS命令来查看当前请求锁的信息。

也可以使用SELECT * FROM information_schema.INNODB_TRX;来查看information_schema架构下的INNODB_TRX来监控当前的事务并分析可能存在的锁。

一致性的非锁定读操作

又称非锁定读,即InnoDB存储引擎通过行多版本控制的方式来读取当前执行时间数据库中行的数据,如果读取的行正在UPDATE,DELETE操作,这时的读取操作不会等待行上锁释放,搜索引擎会去读取行的一个快照数据。非锁定读的机制大大提高了数据读取的并发性,InnoDB存储引擎默认设置的读取方式就是非锁定读,快照其实就是当前行数据的历史版本,可能有多个版本,一个行可能不止一个快照数据,我们称这种技术为行多版本技术,在此基础上的并发控制,又称多版本并发控制(Multi Version Concurrency Control,MVCC).

对于不同的事务隔离级别,非锁定读的读取方式不同:对于READ COMMITTED的事务隔离级别,它总是读取行的最新版本,如果行被锁定,则读取该行版本的最新的一个快照;对于REPEATABLE READ事务隔离级别,它总是读取事务开始时的行数据,

在默认情况下,InnoDB存储引擎的SELECT操作使用一致性非锁定读,但在某些情况下,我们需要对读取操作进行加锁。对SELECT语句支持两种加锁操作:

SELECT ...FOR UPDATE 对读取的行记录加一个X锁,即排他锁,如果其他事务想在这个行上进行操作,都会被阻塞;

SELECT ...LOCK IN SHARE MODE :对读取的行记录加一个S锁,即共享锁,其他事务可以在这个行上使用S锁,但是对于X锁,会阻塞。

自增长和锁

自增长在数据库中是一种常见的属性,在InnoDB中存储引擎的内存结构中,对每个含有自增长值的表都有一个自增长计数器,当对含有自增长计数器的表进行插入操作时,这个计数器会被初始化,在执行插入操作的时候,自增长的计数器值会加1并赋值到自增长列,这种实现方式称作ATUOING Locking,这种锁在完成对自增长值插入的SQL语句后立即释放,因此,带来的顾虑是对于含有自增长列的并发插入的性能降低,因为锁的存在,必须等待上一个插入的完成才能进行下一个插入。

外键和锁

对于一个外键,如果没有显式的对这个列加索引,InnoDB存储引擎自动对其加入一个索引,因为这样可以避免表锁, 对于外键的更新/插入,首先是需要查询父表中的记录(即外键所对应的主键的那个表),即SELECT父表,但对父表我们不是使用一致性非锁定读的方式,而是使用SELECT ...LOCK  IN SHARE MODE 方式,主动对表加入一个S锁,如果这个时候在其它事务中,对该行使用更新或插入语句,则会对该行使用X锁,但是因为之前有S锁,所以会被阻塞。

锁的算法

InnoDB支持三种锁的锁的算法:

⑴Record Lock:单行记录上的锁,锁定索引记录,如果InnoDB存储引擎表建立的时候没有设置任何一个索引,这时InnoDB存储引擎会使用隐式的主键来进行锁定。

⑵Gap Lock:间隙锁,锁定一个范围,但不包括记录本身

⑶Next-Key Lock:Gap Lock + Record Lock ,锁定一个范围,并且锁定记录本身。

注意:在InnoDB存储引擎的默认配置下,事务的隔离级别为REPEATABLE READ的模式,在这个模式下,Next-Key-Lock算法是默认的行记录锁定算法。

锁问题

⑴数据丢失,在并发环境下,使用不同的事务对同一行记录进行更新,如果没有使用锁,则有可能造成更新的丢失,这个时候需要使用锁,锁的意义就是使的并发的操作变为串行操作,即先对表加锁,然后再进行更新。

⑵脏读:首先是脏页,即在缓冲池中已经修改的页,但是没有刷新到磁盘的页。脏数据就是在缓冲池中被修改的数据,并且没有被提交,如果读取到脏数据,则会造成数据不一致,以及违反数据库的隔离性。如何读取脏数据,可以将数据库的隔离级别设置为READ UNCOMMITED,想避免读取脏数据,则改变数据库的隔离级别即可。

⑶ 不可重复读:在一个事务内多次读取同一个数据,在另一个事务中,对该数据进行修改,并进行提交,然后再前一个事务中在此读取这个数据,数据的不一致。不可重复读是可以接受的,因为它读取的是已提交的数据,InnoDB存储引擎中默认的事务隔离级别是READ REPEATABLE.,采用的锁的算法是Next-Key Lock算法。

阻塞

即并发条件下,存在临界资源,对于资源的使用,只能有一个进程使用它,获取资源的锁,并使用临界资源,所以对于其他进程的使用,需要获取锁,如果没有锁则进行等待,即阻塞其进程,直到资源使用完毕,锁的释放,其他进程才可以获取锁,并使用资源。在数据库方面,事务就类比于进程。

死锁

即两个事务互相占有对方的资源,又等待对方释放资源而进行无休止的等待。

 

 

参考《MySQL技术内幕 InnoDB存储引擎》   姜承尧著 ,,  建议亲自读此书,加深理解。