对MySQL加锁的初步理解(一)

博客地址:http://blog.csdn.net/whynottrythis/article/details/50706285

事务的情景:

在并发的情况下,确保请求能够被正确执行。事务保证两个请求‘读取’不到相同的数据结果,执行过程中数据不会被别的会话所修改。

与事务相似的是语言层面的重试机制,当请求因为并发出错时,会发起若干次重试。对重试的条件需要明确,比如只有当$e->getCode() == 1062时才会执行重试。

锁的种类:

  • 共享锁:读锁,允许其他会话获取S锁,但不允许其他会话获取X锁

  • 排它锁:获取X锁之后,不允许其他会话获取该记录

  • 意向锁:意向锁用来表明之后被要求获取的锁。意向共享 (IS) 通过在各资源上放置 S 锁,表明事务的意向是读取层次结构中的部分(而不是全部)底层资源。 意向排它 (IX) 通过在各资源上放置 X 锁,表明事务的意向是修改层次结构中的部分(而不是全部)底层资源。

  • 行锁:record lock 锁住一行记录

  • 间隙’锁:gap lock 锁住某一段范围的记录。间隙锁只会出现在辅助索引上,唯一索引和主键索引没有间隙锁。间隙锁只会阻塞insert操作。

  • 间隙锁:next-key lock 前两种效果的叠加。间隙锁会将B-tree查找的范围数据都加锁,如果查找的数据不存在,InnoDB也会加锁。对于范围查询和不存在的记录,也会加间隙锁,一方面可以防止幻读的现象,另一方面满足恢复和复制的需要。

下面是官网的介绍,Inodb锁的类型:

  • Select,Update和Delete都会在SQL扫描的记录上加锁。InnoDB并不关心满足Where条件的行数。InnoDB does not remember the exact WHERE condition, but only knows which index ranges were scanned.
  • SELECT … FROM is a consistent read, reading a snapshot of the database and setting no locks unless the transaction isolation level is set to SERIALIZABLE. For SERIALIZABLE level, the search sets shared next-key locks on the index records it encounters.
  • SELECT … FROM … LOCK IN SHARE MODE sets shared next-key locks on all index records the search encounters. For index records the search encounters, SELECT … FROM … FOR UPDATE blocks other sessions from doing SELECT … FROM … LOCK IN SHARE MODE or from reading in certain transaction isolation levels. Consistent reads will ignore any locks set on the records that exist in the read view.
  • UPDATE … WHERE … sets an exclusive next-key lock on every record the search encounters.
  • DELETE FROM … WHERE … sets an exclusive next-key lock on every record the search encounters.
  • INSERT sets an exclusive lock on the inserted row. This lock is an index-record lock, not a next-key lock (that is, there is no gap lock) and does not prevent other sessions from inserting into the gap before the inserted row.

Select For Update

Select For Update 的作用期间是从事务的开始到事务的提交。如果没有开启事务,Select的语句是不会被加锁的。引用官方的话如下:

Locking of rows for update using SELECT FOR UPDATE only applies when autocommit is disabled (either by beginning transaction with START TRANSACTION or by setting autocommit to 0. If autocommit is enabled, the rows matching the specification are not locked.

意向锁:

The main purpose of IX and IS locks is to show that someone is locking a row, or going to lock a row in the table.

InnodB支持记录锁共存和锁表。意向锁用来表明该事务接下来将要获取什么类型的锁,有两种类型的意向锁。假设当前事务为T作用在表t上。

  • Intention shared (IS): Transaction T intends to set S locks on individual rows in table t.
  • Intention exclusive (IX): Transaction T intends to set X locks on those rows.

For example, SELECT … LOCK IN SHARE MODE sets an IS lock and SELECT … FOR UPDATE sets an IX lock.

意向锁遵照如下的规则:

  • Before a transaction can acquire an S lock on a row in table t, it must first acquire an IS or stronger lock on t.
  • Before a transaction can acquire an X lock on a row, it must first acquire an IX lock on t.

下面的表格总结了锁之间的兼容处理。

如果事务请求获取的锁与已存在的锁是兼容的,则锁会被授予该事务。如果相冲突的话,该事务就必须等待,直到拥有该锁的事务释放锁。如果请求的锁有冲突,且不能被授予那么就产生死锁。

X IX S IS
X Conflict Conflict Conflict
IX Conflict Compatible Conflict Compatible
S Conflict Conflict Compatible
IS Conflict Compatible Compatible

死锁的例子


  • 官网死锁的例子

The following example illustrates how an error can occur when a lock request would cause a deadlock. The example involves two clients, A and B.

事务A执行如下的语句:
mysql> START TRANSACTION;
mysql> SELECT * FROM t WHERE i = 1 LOCK IN SHARE MODE;

事务B执行如下语句,试着去删除该记录:
mysql> START TRANSACTION;
mysql> DELETE FROM t WHERE i = 1;
该删除操作需要获取X锁,但是因为A事务的S锁和B事务的X锁冲突,所以该锁并不能被授给B。因此事务B会加到该行记录锁的请求队列。

接下来事务A也试着去删除该行记录,这时候,死锁就产生了。
mysql> DELETE FROM t WHERE i = 1;

因为事务A现在需要获取记录的X锁,但是事务B已经在有该记录的X锁请求,正在等待事务A释放S锁。A事务的S锁不能被提升为X锁,因为B事务的请求在A事务的请求之前。于是发生死锁。

  • 间隙锁产生的死锁

    ..

  • 死锁的另一种方式

    如果InnoDB监控输出:“TOO DEEP OR LONG SEARCH IN THE LOCK TABLE WAITS-FOR GRAPH, WE WILL ROLL BACK FOLLOWING TRANSACTION,” 则暗示事务等待锁的数量已经达到200,这个200的定义变量为LOCK_MAX_DEPTH_IN_DEADLOCK_CHECK。等待列表超过200事务会被对待为死锁,事务列表中的事务会被回滚。

    The same error may also occur if the locking thread must look at more than 1,000,000 locks owned by the transactions on the wait-for list. The limit of 1,000,000 locks is defined bY LOCK_MAX_N_STEPS_IN_DEADLOCK_CHECK.

  • 取消死锁

    对于死锁,表现的形式是Sleep,可以通过Show Processlist来查看。如果有些线程执行时间过长,而没有停止,可以输入命令 KILL ID来取消该SQL执行。

    mysql> show processlist;
    +—-+——+———–+——+———+——+——-+——————+
    | Id | User | Host | db | Command | Time | State | Info |
    +—-+——+———–+——+———+——+——-+——————+
    | 7 | root | localhost | yy | Sleep | 154 | | NULL |
    | 8 | root | localhost | NULL | Query | 0 | NULL | show processlist |
    +—-+——+———–+——+———+——+——-+——————+

    分离水平

    事务的隔离级别用来确定事务处理之间的影响程度(同时运行时相互影响的机制),分离水平越高,数据的整合性随之越高, 但同时运行性下降。相反如果分离水平越低,数据整合性降低的同时,运行性提高了。根据数据的用途,分离水平的选择是开发人员必须判断或决定的。分离水平越高,维持锁定的时间久越长,这样同时运行性就会降低。增加死锁产生的概率。降低事务的隔离级别,就可以减小死锁的发生

    分离水平 读脏数据 不可重复读 产生幽灵数据
    READ UNCOMMITTED 未提交读 Y Y Y
    READ COMMITTED 提交读 N Y Y
    REPEATEBLE READ 即可重复读 N N Y
    SERIALIZABLE 可串行化 N N N

    MySQL内部优化加锁实现

    对于MySQL发现索引查询慢于全表扫描的情况,不是使用了期望的行锁,可能会是表锁。

    在解释SQL的时候,会忽略索引。因为它的优化器发现:即使使用了索引,还是要做全表扫描,故而放弃了索引,也就没有使用行锁,却使用了表锁。简单的讲,就是MYSQL无视了你的索引,它觉得与其行锁,还不如直接表锁,毕竟它觉得表锁所花的代价比行锁来的小。 所以mysql 的行锁用起来并不是那么随心所欲的,必须要考虑索引。

    你可能感兴趣的:(mysql,并发,事务)