MySQL锁(二)

MySQL锁(InnoDB)

InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION); 二是采用了行级锁. 行级锁与表级锁本来就有许多不同之处,另外,事务的引入也带来了一些新问题

1.事务

  • InnoDB事务的ACID属性,并发事务带来的问题以及事务的隔离级别,之前的一个笔记已经写过了,不再赘述

2.获取InnoDB行锁争用情况

mysql> show status like 'innodb_row_lock%';

如果发现锁争用比较严重, 如InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比较高,还可以通过设置InnoDB Monitors来进一步观察发生锁冲突的表,数据行等,并分析锁争用的原因

3.InnoDB的行锁模式及加锁方法

InnoDB实现了以下两种类型的行锁

  • 共享锁(S) : 又称读锁, 允许一个事务去读一行, 阻止其他事务获得相同数据集的排他锁. 若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁. 这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改
  • 排他锁(X) : 又称写锁. 允许获取排他锁的事务更新数据, 阻止其他事物取得相同数据级的共享读锁和排他写锁. 若事务T对数据对象A加上X锁, 事务T既可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁

共享锁大家很好理解,就是多个事务只能读数据不能改数据

排他锁并不是说锁住一行数据后,其他事物就不能读取和修改该行数据了. 排它锁指的是一个事务在一行数据加上排它锁之后,其他事务不能在其上加上其他的锁. MySQL InnoDB引擎默认的修改数据语句:update, detele, insert都会自动给涉及到的数据加上排他锁, select 语句默认不会加任何锁类型, 如果加排他锁可以使用 select … for update语句,加共享锁可以使用select … lock in share mode语句. 所以加过排他锁的数据行在其他事物中是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select … from …查询数据,因为普通查询没有任何锁机制.

另外,为了允许行锁和表锁共存, 实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),两种意向锁都是表锁

  • 意向共享锁(IS) : 事务打算给数据行共享锁, 事务在给一个数据行加共享锁之前必须先取得该表的IS锁.
  • 意向排他锁(IX) : 事务打算给数据行排他锁, 事务在给一个数据行加排他锁之前必须先取得该表的IX锁.

InnoDB行锁模式兼容性列表

X IX S IS
X 冲突 冲突 冲突 冲突
IX 冲突 兼容 冲突 兼容
S 冲突 冲突 兼容 兼容
IS 冲突 兼容 兼容 兼容

如果一个事务请求的锁模式与当前的锁兼容,那么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方式获得排他锁

4.InnoDB行锁实现方式

InnoDB行锁是通过给索引上得到索引项加锁来实现的,与Oracle不同, 后者是通过在数据块中对应数据行加锁来实现的. InnoDB这种行锁实现特点意味着 : 只有通过索引条件检索数据,InnoDB才使用行级锁, 否则,InnoDB将使用表锁

在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能

例1: 如果一个表中没有加索引,那么在一个事务中给一行数据加了排他锁, 另一个事务在请求其他行的锁时,会出现锁等待. 原因就是在没有索引的情况下,InnoDB只能使用表锁. 当我们给其增加一个索引后,InnoDB就只锁定了符合条件的行

例2: 由于MySQL行锁是针对索引加的锁,不是针对记录家的所, 所以虽然是访问不同行的记录, 但是如果是使用相同的索引键,是会出现锁冲突的.

例3: 当表中有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引,唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁

例4: 即便在条件中使用了索引字段, 但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的, 如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁. 因此,在分析冲突时,要检查SQL执行计划,以确认是否真正使用了索引

通过在SQL语句前加一句 explain 可以查看SQL语句的执行计划

5.间隙锁(Next-Key)

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

举例来说,假如emp表中只有101条记录,其empid的值分别是1,2,…100,101,下面的SQL

SELECT * FROM emp WHERE empid > 100 FOR UPDATE

是一个范围条件的检索,InnoDB不仅会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的"间隙"加锁.

InnoDB使用间隙所的目的,一方面是为了防止幻读,以满足相关隔离级别的要求,对于上面的例子,如果不使用间隙锁,其他事务插入了empid大于100的任何记录,那么本事务如果再次执行上述语句,就会发生幻读;另一方面,是为了满足其恢复和复制的需要.

很显然,在使用范围条件检索并锁定记录时,InnoDB这种加锁机制会阻塞符合条件范围内键值的并发插入,这往往会造成严重的锁等待**.因此,在实际开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件.**

还要特别说明的是,InnoDB除了通过范围条件加锁时使用间隙锁外,如果使用相等条件给一个不存在的记录加锁,InnoDB也会使用间隙锁.

小结:对于InnoDB表,主要讨论了以下几项内容

(1)InnoDB的行锁是基于索引实现的,如果不通过索引访问数据,InnoDB会使用表锁。
(2)介绍了InnoDB间隙锁(Next-key)机制,以及InnoDB使用间隙锁的原因。
在不同的隔离级别下,InnoDB的锁机制和一致性读策略不同。

在了解InnoDB锁特性后,用户可以通过设计和SQL调整等措施减少锁冲突和死锁,包括:

尽量使用较低的隔离级别; 精心设计索引,并尽量使用索引访问数据,使加锁更精确,从而减少锁冲突的机会;
1> 选择合理的事务大小,小事务发生锁冲突的几率也更小;
2> 给记录集显式加锁时,最好一次性请求足够级别的锁。比如要修改数据的话,最好直接申请排他锁,而不是先申请共享锁,修改时再请求排他锁,这样容易产生死锁;
3> 不同的程序访问一组表时,应尽量约定以相同的顺序访问各表,对一个表而言,尽可能以固定的顺序存取表中的行。这样可以大大减少死锁的机会;
4> 尽量用相等条件访问数据,这样可以避免间隙锁对并发插入的影响; 不要申请超过实际需要的锁级别;除非必须,查询时不要显示加锁;
5> 对于一些特定的事务,可以使用表锁来提高处理速度或减少死锁的可能。

猜你喜欢

转载自blog.csdn.net/wintershii/article/details/88914957