MySQL可以对事务设置隔离级别,保证数据不出现问题。事务的隔离级别1、2、3、4等级是靠什么实现的?那就是MySQL的锁机制——某一事务通过加锁暂时获得某些数据的独占读写操作权限。隔离级别越高,说明“锁得越严重”,付出的代价也就越大。
只有InnoDB支持事务,我们主要以“InnoDB”为主介绍锁,“MyISAM”的锁后面也会介绍的。
从上图中我们可获得一个信息,锁分为表锁、行锁、页锁。这是根据锁定数据的范围做的划分。
表锁意思是为整个表加一把锁,把一幢大楼当成一个数据表,这把锁就是楼大门的锁。
行锁意思就是可以为某行记录加锁,不影响其他行的记录被操作。这把锁就是楼里面每个房间的锁。
页锁它是比表锁小比行锁大的锁,我们就理解为每层楼的锁吧。
三种锁的特性可大致归纳如下:
1) 表锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
2) 行锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
3) 页锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
什么是死锁,需要后面介绍。
InnoDB是支持行锁的,行锁是我们了解的重点。InnoDB支持行锁并不意味就一定加行锁。InnoDB行锁是通过给索引上的索引项加锁来实现的,因此InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行锁,否则InnoDB将使用表锁!(这个地方后面有详解)
根据锁定范围可划分为表锁、行锁、页锁,MySQL的数据主要有增删查改四种操作,行锁根据锁定后可操作的权限又分为:
共享锁(S):一个事务加锁后其他事务只能获取相同数据集的共享锁,不能获取排他锁,也就是说所有事务只能读不能写。
排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享锁和排他锁,也就是说其他事务不能读也不能写,只有加锁的事务可写可读。X锁用的最多。
再说两种锁:
意向共享锁(IS):事务打算给数据行加共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
意向排他锁(IX):事务打算给数据行加排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。
说明:共享锁和排他锁都是行锁,意向锁都是表锁,应用中我们只会使用到共享锁和排他锁,意向锁是mysql内部使用的,不需要用户干预。
如果一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。
共享锁与排他锁怎么去用?
1)对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);
2)对于普通SELECT语句,InnoDB不会加任何锁,事务可以通过以下语法给select语句加共享锁或排他锁。
共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE。
排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE。
共享锁与排他锁什么时候解锁?
事务结束锁就解除了。
MySQL加锁的原则是2PL (二阶段锁):Two-Phase Locking。2PL比较容易理解,说的是锁操作分为两个阶段:加锁阶段与解锁阶段,并且保证加锁阶段与解锁阶段不相交。
从上图可以看出,2PL就是将加锁/解锁分为两个完全不相交的阶段。加锁阶段:只加锁,不放锁。解锁阶段:只放锁,不加锁。
额外说明:如果没有开启事务,只是select... for update 加X锁(排他锁)是没什么用的,获取完结果集立马就解锁了。如果不去修改数据,独占读取数据没什么意义。
可以通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况:
如InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比较高,说明锁争用比较严重。