暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

innodb存储引擎读书笔记:锁

架构思考 2020-08-09
389

myisam引擎的锁是表锁,在并发插入时性能较差。

innodb实现了两种行级锁:

  1. 共享锁(S lock)

  2. 排他锁(X lock)


当一个事务获取了行r的共享锁是,其他的事务也可以获取行r的共享锁,如果其他事务想获取行r的排他锁,必须等待共享锁释放。


innodb允许行级锁和表级锁同时存在。

innodb实现了意向锁,意向锁是表级锁。意向锁有两种:

  1. 意向共享锁(IS lock):表示事务想获取表中某几行的共享锁

  2. 意向排他锁(IX lock):表示事务想获取表中某几行的排他锁


意向锁不会阻塞除全表扫描之外的任何请求。



information_schema.INNODB_TRX表的字段有:

  1. trx_id:innodb引擎内部唯一的事务id

  2. trx_state:事务的当前状态

  3. trx_started:事务的开始时间

  4. trx_request_lock_id:如果trx_state为LOCK WAIT,则此字段的值表示事务等待的锁的id;如果trx_state不为LOCK WAIT,则此字段为null

  5. trx_wait_started:事务开始等待的时间点

  6. trx_weight:事务的权重,反映了一个事务修改和锁住的行数。当发生死锁需要回滚的时候,innodb会选择权重最小的事务进行回滚。

  7. trx_mysql_thread_id:事务对应的线程的id

  8. trx_query:事务中的sql


information_schema.innodb_locks表的字段有:

  1. lock_id:锁的id

  2. lock_trx_id:获取了锁的事务的id

  3. lock_mode:锁的模式

  4. lock_type:锁的类型,表锁还是行锁

  5. lock_table:对哪个表加了锁

  6. lock_index:对哪个索引加了锁

  7. lock_space:表空间的id

  8. lock_page:被锁住的页的数量,lock_type为表锁时该字段为null

  9. lock_rec:被锁住的行的数量,lock_type为表锁时该字段为null

  10. lock_data:被锁住的行的主键,lock_type为表锁时该字段为null


information_schema.innodb_lock_waits的字段有:

  1. requesting_trx_id:申请锁的事务的id

  2. requesting_lock_id:申请的锁的id

  3. blocking_trx_id:哪个事务阻塞了requesting_trx_id

  4. blocking_lock_id:哪个锁阻塞了requesting_lock_id


示例如下(为什么blocking_lock_id和requested_lock_id不是同一个锁?导致当前事务阻塞的锁竟然不是当前事务正在请求的锁?确实不一样,事务730FEE对行记录加了X锁,而事务7311F4要对该行记录加S锁。):



一致性的非锁定读:innodb通过多版本控制读取当前时刻数据库中的行记录。如果读取的行记录正在被delete、update,读取操作不等待行上锁的释放,而会去读取该行的一个快照。这个快照实际上是undo段。一致性的非锁定读是innodb默认的读取方式。


多版本并发控制MVCC:multi version concurrency controll。

多版本指的是快照有多个版本。


在repeatable read(innodb默认的事务隔离级别)和read committed下,innodb使用一致性非锁定读,但是对快照的定义不同。


read committed下,一致性非锁定读总是读取最新的快照。

repeatable read下,一致性非锁定读总是读取事务开始时的行数据版本。


(突然想到的一个疑问:事务内的每一次对行记录的操作,是否会操作完就释放锁?还是说会等到事务提交之后一起释放?

验证:

先在事务A中获取行记录上的S锁:

再在事务B中获取该行记录上的X锁:

可以看到事务B阻塞了。查看INNODB_TRX表也可以看到如下信息:

因此说明了事务中获取的S锁要等到事务结束后再释放,X锁也是如此。这个特性与事务的隔离级别无关。



查看事务隔离级别:

select @@tx_isolation;


设置事务隔离级别:

set [global | session] transaction isolation level Read uncommitted | Read committed | Repeatable Read | Serializable;


如果选择global,则隔离级别将应用于之后新建的session,而当前已经存在的session(包括当前session)不受影响。

如果选择session,则隔离级别将应用于当前session内之后的所有事务,其他已经存在的session和之后新创建的session均不受影响。



如果事务隔离级别为repeatable read,则session A中的三次读都会读到id=1的记录。如果事务隔离级别为read committed,则session A中的前两次读都会读到id=1的记录,但是第三次读会返回空集。(已验证)



innodb中select操作默认使用一致性非锁定读。

如果到对select操作加锁:

    select ... for update:对读取的行记录加X锁,其他事务如果要在该行上加任何锁都会被阻塞。

    select ... in share mode:对读取的行记录加S锁,其他事务可以对该行加S锁,但是要加X锁就会被阻塞。


注意:

1. select操作加锁后,其他事务仍然可以执行一致性非锁定读select操作。

2. 事务提交后select上的锁才会被释放。默认情况下,执行一条语句就是一次事务,所以select ... for update和select ... in share mode执行完后锁就被释放。



AUTO-INC Locking:在执行插入操作时,自增长列(一般就是指主键)的值由自增长计数器确定。从自增长计数器中取值时,需要先上锁。

这种锁是表级锁,但是却不会在事务完成之后才释放,而是在完成插入操作后立即释放。


参数innodb_autoinc_lock_mode:

取0时表示上文所提的加锁方式;

取1(默认值)时表示在进行simple inserts(插入前就能确定插入的行数)时采用互斥量对计数器进行累加,在进行bulk inserts(插入前不能确定插入的行数)时采用上文提到的加锁方式。

取2时表示对于所有的insert操作都采用互斥量。


innodb中,如果一个表中定义了自增长列,则该列上必须建索引,如果联合索引中包含该列,则该列必须是第一列。


innodb中,外键列会自动被加上一个索引,避免表锁。

插入或更新外键列时,首先会select父表,为保证数据一致,会使用select ... lock in share mode,而不是一致性非锁定读。



innodb中行锁分类:

  1. record lock:行记录上的锁

  2. gap lock:间隙锁,锁定一个范围,但不包含记录本身

  3. next-key lock:等价于record lock + gap lock。


innodb中,默认的事务隔离级别是repeatable read。这种隔离级别下,next-key lock是默认的行记录锁定算法。


repeatable read隔离级别下,先在事务A中执行select * from t where a < 6 lock in share mode,再在事务B中执行insert into t select 5或insert into t select 6,事务B会阻塞住。如果在事务B中执行insert into t select 9,则会直接执行成功。事务A中的select会被加上next-key share lock。


repeatable read隔离级别下,先在事务A中执行select * from t where a = 7 lock in share mode,再在事务B中执行insert into t select 5或insert into t select 6,则会直接执行成功。事务A中的select会被加上record share lock。





lost update:

场景(时间线顺序):

  1. 事务1查询一行记录

  2. 事务2也查询该行记录

  3. 事务1更新该行记录

  4. 事务2更新该行记录

结果是事务1的更新被覆盖了。


如何避免?

事务在查询时就加上排他锁。





脏数据:已被修改但还未被提交的数据。

脏读:一个事务读到另一个事务中的脏数据。

隔离级别为read uncommitted时就会出现脏读。




不可重复读:同一个事务内,多次读同一行记录,读到的数据不同。原因是,在该事务内的两次select操作之间,其他事务修改了该行记录,并提交了。

隔离级别为read committed时会出现不可重复读。




事务之间可能会因锁的问题产生阻塞。

innodb_lock_wait_timeout:超时时间,可以在运行时修改。默认是50s。

innodb_rollback_on_timeout:超时是是否对事务进行回滚,不可在运行时修改。默认是OFF。




mysql中,事务中抛出异常时,mysql一般不会对事务进行回滚,而是等待用户决定是要提交还是回滚。但是发生死锁时,innodb会自动对其中一个事务进行回滚。

文章转载自架构思考,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论