概述

锁是计算机协调多个进程或线程并发访问某一资源的机制。MySQL不同的存储引擎支持不同的锁机制。MyISAM/MEMORY:表级锁(table-level locking);

BDB(被InnoDB取代):页面锁(page-level locking),但也支持表级锁;

InnoDB:既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。

不同性质的锁特性不同:表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。

行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

相同性质的锁根据模式又分为读锁和写锁(以myISAM为例):

MyISAM表的读操作与写操作之间,以及写操作之间是串行的。当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。读锁(共享锁):不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求

写锁:会阻塞其他用户对同一表的读和写操作;

MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁不需要用户干预,因此,用户一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁。

MyISAM的读写锁是写优先,获得锁后其他线程不能做任何操作,大量的更新使得查询很难得到锁,造成阻塞。所以表锁偏读,行锁偏写。Table_locks_immediate:表级锁锁定的次数,每次立即获取锁值加1;

table_lock_watied:出现表级锁争用而发生等待的次数(数值高说明存在锁争用的情况);

行锁(InnoDB)

InnoDB实现了以下两种类型的行锁:共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁;

排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。

为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。

意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。锁兼容性

应用场景实现事务(如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。);

意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁;事务可以通过以下语句显示给记录集加共享锁或排他锁。共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE

排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE

用SELECT ... IN SHARE MODE获得共享锁,主要用在需要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操作。但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁,对于锁定行记录后需要进行更新操作的应用,应该使用SELECT... FOR UPDATE方式获得排他锁。事务实现依靠锁解决库存问题悲观锁实现:select语句加入一个行锁,和更新库存的语句互斥,保证在查询的时候库存不被修改;

select kc from skuinfo for update乐观锁实现:select添加一个版本字段,每次更新时查询和更新版本字段,如果版本字段发生变化,sql语句不会执行成功

select kc ,version from skuinfo where sku_id=?

update sku_info set kc=kc-1,version=version+1 where sku_id=? and version=versionn

行锁变表锁

InnoDB行锁是通过给索引上的索引项加锁来实现的:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!

无索引或索引失效导致行锁变表锁:索引未失效时其他session可以成功提交索引失效后,其他session不可以提交

间隙锁

当使用分为条件而不是相等条件检索数据局,并请求共享或排他锁时,InnoDB会给复合条件的已有数据索引项加锁;对于键值在条件范围内但是不存在的记录,叫做间隙(GAP)。InnoDB也会对间隙加锁,这种锁机制叫做间隙锁。

当一个键值范围被锁定后,造成插入在锁定键值范围内的数据无法插入。

死锁

发生死锁后,InnoDB一般都能自动检测到,并使一个事务释放锁并回退,另一个事务获得锁,继续完成事务。但在涉及外部锁,或涉及表锁的情况下,InnoDB并不能完全自动检测到死锁,这需要通过设置锁等待超时参数 innodb_lock_wait_timeout来解决。

表锁选用事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高该事务的执行速度;

事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销。