从数据操作的粒度上可以分为如下三种:
- 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
- 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
- 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
仅从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用;而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理系统。
从数据操作的类型上可以分为如下两种:
- 共享锁(Share Locks,记为S锁),针对同一份数据,多个读操作可以同时进行,不会互相影响
- 排他锁(eXclusive Locks,记为X锁),当前写操作没有完成前,它会阻断其他写锁和读锁
为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁:意向共享锁和意向排他锁,这两种意向锁都是表锁。一个事务在给数据行加锁之前必须先取得对应表对应的意向锁。意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE 和INSERT 语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB 不会加任何锁;事务可以通过语句显式给记录集加共享锁或排他锁。
乐观锁和悲观锁
乐观锁与悲观锁是两种并发控制的思想,可用于解决丢失更新问题。
乐观锁会“乐观地”假定大概率不会发生并发更新冲突,访问、处理数据过程中不加锁,只在更新数据时再根据版本号或时间戳判断是否有冲突,有则处理,无则提交事务。用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。
悲观锁会“悲观地”假定大概率会发生并发更新冲突,访问、处理数据前就加排他锁,在整个数据处理过程中锁定数据,事务提交或回滚后才释放锁。另外与乐观锁相对应的,悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。
行锁模式
InnoDB行锁是通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!
当一个事务需要给某个资源加锁的时候,如果遇到一个共享锁正锁定着自己需要的资源,它会再加一个共享锁,不过不能加排他锁。但是,如果遇到须要锁定的资源已经被一个排他锁占有,则只能等待该锁定释放资源之后它才能获取锁定资源并添加自己的锁定。而意向锁的作用就是当一个事务在须要获取资源锁定的时候,如果遇到需要的资源已经被排他锁占用,该事务可以在锁定行的表上添加一个合适的意向锁。如果需要一个共享锁,那么就在表上面添加一个意向共享锁。而如果需要的是在某行(或某些行)上面添加一个排他锁,则先在表上面添加一个意向排他锁。意向共享锁可以同时并存多个,但是意向排他锁只能有一个存在。
【记录锁(Record Locks)】
单个行记录上的锁。对索引项加锁,锁定符合条件的行。其他事务不能修改和删除加锁项.【间隙锁(Gap Locks)】
【临键锁(Next-key Locks)】
临键锁,是记录锁与间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间。(临键锁的主要目的,也是为了避免幻读(Phantom Read)。如果把事务的隔离级别降级为RC,临键锁则也会失效。)Next-Key 可以理解为一种特殊的间隙锁,也可以理解为一种特殊的算法。通过临建锁可以解决幻读的问题。每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。需要强调的一点是,InnoDB 中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。死锁
死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环。当事务试图以不同的顺序锁定资源时,就可能产生死锁。多个事务同时锁定同一个资源时也可能会产生死锁
发生死锁后,InnoDB一般都能自动检测到,并使一个事务释放锁并回退,另一个事务获得锁,继续完成事务。但在涉及外部锁,或涉及表锁的情况下,InnoDB并不能完全自动检测到死锁,这需要通过设置锁等待超时参数 innodb_lock_wait_timeout来解决。需要说明的是,这个参数并不是只用来解决死锁问题,在并发访问比较高的情况下,如果大量事务因无法立即获得所需的锁而挂起,会占用大量计算机资源,造成严重性能问题,甚至拖跨数据库。我们通过设置合适的锁等待超时阈值,可以避免这种情况发生。