MySQL-lock(锁)-v2.0

概念

  • 对共享资源进行并发访问

  • 保证数据的完整性和一致性

每个数据库的锁的实现是完全不同的。在MySQL中:

  • MyISAM是表锁

  • InnoDB是行级锁(和Oracle SQL Server的行锁不一样 (SQL Server有页级别的锁))

区别

要注意数据库中的锁(lock)与程序中的锁(latch)的区别:

Lock Latch
对象 事务 线程
保护 数据库内容 内存数据结构
持续时间 整个事务过程 临界资源
模式 行锁,表锁,意向锁 读写锁,互斥量
死锁 通过waits-for graph(等待图), time out 等机制进行死锁检测和分析 无死锁检测与处理机制。仅通过应用程序加锁的顺序保证无死锁的情况发生。
存在 Lock Manager的哈希表中 每个数据结构的对象中

Tips:

  • 在应用程序中,如果使用互斥量mutex和读写锁rw-lock,有个原则是它持有的时间越短越好,如果持有的时间太长,那说明程序不好
  • 进入临界资源的时间应该越短越好,但数据库中的锁 锁的是整个事务过程,锁住的时间是根据事务时间而来的,数据库中的锁有 表锁, 行锁,意向锁, 程序中的latch有读写锁,互斥量, 应用程序中的latch没有死锁检测。

MySQL中Latch的查看:

mysql> show engine innodb mutex;
  +--------+------------------------+---------+
  | Type   | Name                   | Status  |
  +--------+------------------------+---------+
  | InnoDB | rwlock: log0log.cc:838 | waits=9 |
  +--------+------------------------+---------+
  1 row in set (0.10 sec)

Tips:通常来说,这些mutex没有多大作用,主要是给内核DBA来看的。对于普通的DBA,要看的是数据库中的锁,Latch不是要关注的,只需要知道InnoDB也是有Latch的,因为一个程序肯定需要对共享资源进行并发 访问,Latch是轻量级的锁,持有的时间非常短,持有即释放。

InnoDB中的锁

  • S 行级共享锁 表示可以并发进行访问

  • X 行级排他锁 有一个线程或会话占用个这个行级别的锁,那其他事务就不能去使用

  • IS意向S锁 事务想要获得一张表中某几行的共享锁

  • IX意向X锁 事务想要获得一张表中某几行的排他锁

  • AI(auto_increment)自增锁 淘宝数据库内核月报中关于这把锁的介绍

Tips: 意向锁揭示下一层级请求的锁类型,即:下一层加一把什么样的锁。 ​ InnoDB存储引擎中的意向锁都是表锁

各种锁的兼容性
兼容: o(无需等待) 互斥:x(需等待) s锁 x锁 ls锁 lx锁
s锁 o x o x
x锁 x x x x
ls锁 o x o o
lx锁 x x o o

意向锁全部都是兼容的(当前层是兼容的,下一层再说),S和S锁是兼容的S和X锁是不兼容的。

如果表上加了一个X锁,代表在表层级别进行加锁,后面的线程都不能在下一层级别进行加锁(IS,IX,S,X锁都会发生等待)

数据库加锁过程是这样的:

MySQL-lock(锁)-v2.0_第1张图片
加锁

如果想对row_id=1的记录进行加锁,就分别在库,表,页上加IS锁,然后在记录上加S锁,加锁时不是直接对记录加锁的,而是有一个树的结构,对所有层次都进行加锁,其他层次因为要加的不在它们上面,所以要加的不是S锁,而是意向锁,表示下一层级要加锁的类型。

Q: 为什么意向锁之间是相互兼容的
Q: 为什么要设计意向锁(为实现多粒度加锁)
Q:为什么不直接加锁,而是有一个层级表示呢? 这是为了多层锁的实现。

MySQL中没有数据库和页级别的锁,只有表锁和记录锁,所以说,InnoDB中的意向锁都是表锁,加锁都是从意向锁开始加起的,不能直接加最低级别的锁。

如果没有意向锁,现在想在表上加锁,但是表锁无法知道在下一层级记录的情况.

如果没有意向锁机制,只有记录锁,那记录锁就是记录锁,如何实现表锁呢?每一条记录加锁,但不能保证有其他事务在并发的使用这条记录,不能实现表锁。

锁的信息是保存在内存中的。

innodb中的锁都是表锁,那给表加锁时怎么办,直接加锁吗? 有些小小不一样,mysql中的表锁和innodb中的表锁有些不一样,通常来说,在innodb内部 通常不会在表级别加S,X锁的. 加S锁,在源代码中有一个例子,加X锁,在源代码中没有找到例子,什么时候会在表级别加S锁呢? 在线修改 (表锁是在上层实现,不是在引擎层实现的)通常来说都是在表级别加意向锁的,表级别是不会发生等待的,但是有种特别情况:alter table 并且要对表加一个索引,add index创建索引的时候会对表加一个S锁,如果在加索引的过程中,有其他事务是IS锁,要对一条记录进行查询,是可以执行的,但是如果有另外一个线程,执行要对记录加X锁的,S和IX在表层级互斥,就要发生等待 之前mysql加索引过程中,可以发现加索引对读的操作是没有影响的,但写的事务就得等待了。这就是在innodb中,唯一在表级别加S锁的情况:alter table add index . (因为索引要排序)

MySQL 如何加锁

锁的查询:

> SHOW ENGINE INNODB STATUS;
 mysql> desc lock_test_1;
  +-------+---------+------+-----+---------+-------+
  | Field | Type    | Null | Key | Default | Extra |
  +-------+---------+------+-----+---------+-------+
  | a     | int(11) | NO   | PRI | NULL    |       |
  | b     | int(11) | YES  |     | NULL    |       |
  | c     | int(11) | YES  |     | NULL    |       |
  +-------+---------+------+-----+---------+-------+
  3 rows in set (0.00 sec)
  ​
  mysql> select * from lock_test_1;
  +---+------+------+
  | a | b    | c    |
  +---+------+------+
  | 1 |    4 |    3 |
  +---+------+------+
  1 row in set (0.00 sec)
  ​
  mysql> begin;
  Query OK, 0 rows affected (0.00 sec)
  ​
  mysql> select * from lock_test_1 where a = 1 for update;  #加排它锁
  +---+------+------+
  | a | b    | c    |
  +---+------+------+
  | 1 |    4 |    3 |
  +---+------+------+
  1 row in set (0.01 sec)
  ​
  mysql> select * from lock_test_1 where a = 1 lock in share mode;  #加共享锁
  ​
  mysql> pager less
  mysql> show engine innodb status\G\c
  ...
  ---TRANSACTION 3353, ACTIVE 3045 sec
  2 lock struct(s), heap size 1136, 1 row lock(s)
  MySQL thread id 9, OS thread handle 123145543925760, query id 133 localhost root
  TABLE LOCK table `test_mysql`.`lock_test_1` trx id 3353 lock mode IX
  RECORD LOCKS space id 46 page no 3 n bits 72 index PRIMARY of table `test_mysql`.`lock_test_1` trx id 3353 lock_mode X locks rec but not gap
  Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
   0: len 4; hex 80000001; asc     ;;          #记录
   1: len 6; hex 000000000d17; asc       ;;    #事务ID
   2: len 7; hex b4000001280110; asc     (  ;;    #回滚指针
   3: len 4; hex 80000004; asc     ;;          #记录
   4: len 4; hex 80000003; asc     ;;          #记录
  ...                                        
  ​
  mysql> set  global innodb_status_output_locks=1;
  mysql> show variables like "%wait%timeout%";
  +--------------------------+----------+
  | Variable_name            | Value    |
  +--------------------------+----------+
  | innodb_lock_wait_timeout | 50       |  # 锁超时的设置变量(如果一个事务加不上锁,会发生超时)
  +--------------------------+----------+
  3 rows in set (0.00 sec)

mysql5.6版本已经不会对表加S锁了,5.6出现online DDL功能的支持 对于很多DDL操作都是在线的了,读写都是可以的。

InnoDB有几张源数据表(在information_schema中)保存了事务和锁的信息

  • innodb_trx(查看执行的事务),
mysql> select * from innodb_trx\G
  *************************** 1. row ***************************
                      trx_id: 3354
                   trx_state: RUNNING
                 trx_started: 2018-10-27 15:41:07
       trx_requested_lock_id: NULL
            trx_wait_started: NULL
                  trx_weight: 2
         trx_mysql_thread_id: 10
                   trx_query: NULL
         trx_operation_state: NULL
           trx_tables_in_use: 0
           trx_tables_locked: 1
            trx_lock_structs: 2
       trx_lock_memory_bytes: 1136
             trx_rows_locked: 1
           trx_rows_modified: 0
     trx_concurrency_tickets: 0
         trx_isolation_level: REPEATABLE READ
           trx_unique_checks: 1
      trx_foreign_key_checks: 1
  trx_last_foreign_key_error: NULL
   trx_adaptive_hash_latched: 0
   trx_adaptive_hash_timeout: 0
            trx_is_read_only: 0
  trx_autocommit_non_locking: 0
  1 row in set (0.00 sec)
  • innodb_locks(持有的锁)
mysql> select * from innodb_locks\G
  *************************** 1. row ***************************
      lock_id: 3355:46:3:2
  lock_trx_id: 3355
    lock_mode: S
    lock_type: RECORD
   lock_table: `test_mysql`.`lock_test_1`
   lock_index: PRIMARY
   lock_space: 46
    lock_page: 3
     lock_rec: 2
    lock_data: 1
  *************************** 2. row ***************************
      lock_id: 3354:46:3:2
  lock_trx_id: 3354
    lock_mode: X
    lock_type: RECORD
   lock_table: `test_mysql`.`lock_test_1`
   lock_index: PRIMARY
   lock_space: 46
    lock_page: 3
     lock_rec: 2
    lock_data: 1
  2 rows in set, 1 warning (0.00 sec)
  • innodb_lock_waits(事务等待的事务)
mysql> melect * from innodb_lock_waits\G
  *************************** 1. row ***************************
  requesting_trx_id: 3355
  requested_lock_id: 3355:46:3:2
    blocking_trx_id: 3354
   blocking_lock_id: 3354:46:3:2
  1 row in set, 1 warning (0.00 sec)

建议用这三张表来查看当前事务的锁。(5.5 -5.6 版本中有的表)

  SELECT
    r.trx_id waiting_trx_id,
  r.trx_mysql_thread_id waiting_thread, 
    r.trx_query waiting_query,
  b.trx_id blocking_trx_id, 
  b.trx_mysql_thread_id blocking_thread, 
    b.trx_query blocking_query
  FROM information_schema.innodb_lock_waits w INNER JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id
  INNER JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id
  ​
  *************************** 1. row ***************************
   waiting_trx_id: 281479629835840
   waiting_thread: 11
    waiting_query: select * from lock_test_1 where a = 1 lock in share mode
  blocking_trx_id: 3356
  blocking_thread: 10
   blocking_query: NULL
  1 row in set, 1 warning (0.00 sec)
  ​
  # 缺点:看不到阻塞的SQL语句

MySQL 5.6开始还有个更牛逼的元信息库:

mysql> select * from innodb_lock_waits\G
  *************************** 1. row ***************************
                  wait_started: 2018-10-27 16:12:42
                      wait_age: 00:00:09
                 wait_age_secs: 9
                  locked_table: `test_mysql`.`lock_test_1`
                  locked_index: PRIMARY   # InnoDB 的锁都是在索引上面
                   locked_type: RECORD
                waiting_trx_id: 281479629835840
           waiting_trx_started: 2018-10-27 16:12:42
               waiting_trx_age: 00:00:09
       waiting_trx_rows_locked: 1
     waiting_trx_rows_modified: 0
                   waiting_pid: 11
                 waiting_query: select * from lock_test_1 where a = 1 lock in share mode
               waiting_lock_id: 281479629835840:46:3:2
             waiting_lock_mode: S
               blocking_trx_id: 3356
                  blocking_pid: 10
                blocking_query: NULL
              blocking_lock_id: 3356:46:3:2
            blocking_lock_mode: X
          blocking_trx_started: 2018-10-27 16:08:16
              blocking_trx_age: 00:04:35
      blocking_trx_rows_locked: 1
    blocking_trx_rows_modified: 0
       sql_kill_blocking_query: KILL QUERY 10
  sql_kill_blocking_connection: KILL 10
  1 row in set, 3 warnings (0.01 sec)

遗憾的是在MySQL中,锁的历史信息是看不到的。

锁与事务隔离级别

先看四个概念:

  • locking

  • concurrency control

  • isolation

  • serializability

这四个概念在数据库中是一个概念:锁是用来实现并发控制的,并发控制用来实现隔离级别,同样隔离级别是通过锁来控制的,而锁的目的是为了使得事务之间的执行时序列化的(串行化)。

并发控制准则:并发不能导致程序出错, 不能导致吞吐率降低或者响应时间更快(Concurrent execution should not have lower throughput or much higher response times than serial execution.)

事务隔离级别

隔离性 : 多个事务并行执行,好似是串行执行的;一个事务所做的修改对其他事务是不可见的,好似是串行执行的。

事务隔离级别有四种:

  • READ UNCOMMITTED

  • READ COMMITTED

    • Oracle、DB2、SQL Server的默认事务隔离级别...
  • REPEATABLE READ

  • SERIALIZABLE

Oracle支持SERIALIZABLE和READ COMMITTED

SQL Server 和 MySQL四个级别都支持

但这样没有解决事务可能存在的问题,如:

在不同的事务隔离级别下,存在不同的问题:如:不可重复读, 幻读,脏读.

脏读 : 能读到未提交事务的状态.

不可重复读:一个事务里执行两条相同的sql 对同一记录执行结果不同

幻读( phantom read) 连续执行两次同样的SQL语句可能导致不同的结果,第二次的SQL语句可能会返回之前不存在的行

在标准的事务隔离级别中:

  • READ UNCOMMITTED 三种问题都存在

  • READ COMMITTED 解决了脏读问题

  • REPEATABLE READ 解决了脏读和不可重复读问题

  • SERIALIZABLE 是标准的隔离级别,解决了脏读,幻读,不可重复读 问题。

MySQL的InnoDB在RR级别解决了幻读问题(使用了next-key lock algorithm)。

举例:

 # 首先查看事务隔离级别
  mysql> select @@global.transaction_isolation;
  +-------------------------+
  | @@transaction_isolation |
  +-------------------------+
  | READ-UNCOMMITTED        |
  +-------------------------+
  1 row in set (0.00 sec)
  select @@global.tx_isolation, @@tx_isolation; 
脏读

在一个session中,显示:

MySQL-lock(锁)-v2.0_第2张图片
image.png

在另一个进程中,开启一个事务:

MySQL-lock(锁)-v2.0_第3张图片
image.png

未提交的数据,另一个session却可以读到:


MySQL-lock(锁)-v2.0_第4张图片
image.png

脏读 : 能读到未提交事务的状态.

不可重复读:

一个session中:

MySQL-lock(锁)-v2.0_第5张图片
image.png

此时在另一个session中:

image.png

此时在第一个session中:

MySQL-lock(锁)-v2.0_第6张图片
image.png

不可重复读:一个事务里执行一条相同的sql对同一记录执行结果不同

幻读

一个事务中:连续执行两次 select * from t where a <= 30 for update
会得到不同的结果

幻读( phantom read) 连续执行两次同样的SQL语句可能导致不同的结果,第二次的SQL语句可能会返回之前不存在的行

比如:

事务1: SELECT*FROM tb_a WHERE A<=13; 对于READ COMMITTED,返回的是10,11,13 此时事务2 插入12,再执行sql语句,得到的是10,11,12,13,两次返回的结果不同,12是之前没有的,这就叫做幻读,在InnnoDB中,锁住的是(-∞,10],(10,11],(11,13]这三个范围。 事务2要想插入,需要等待,这样就解决了幻读,第二次执行SELECT也不会有12这条记录

但是这样是有代价的:并发度低。12这条记录不能插入了,要获得更大的并发性,可以把隔离级别调成READ COMMITTED

记录锁的类型

  • Record Lock

    • 单个行记录上的锁
  • Gap Lock

    • 锁定一个范围,但不包含记录本身
  • Next-key Lock

    • Gap Lock + Record Lock,锁定一个范围,并且锁定记录本身

举例:

假设有 id 为 10 20 30 的几条记录

  • Record Lock:之锁住10 20 30 这些记录本身。
  • Gap Lock:当锁10时,锁住的是(-∞,10) 这样的一个范围,锁20时,锁的是 (10,20)以此类推
  • Next-key Lock:是前面两个算法的结合,当锁10时,锁住的是(-∞,10],锁20时,锁的是(10,20], 锁住20本身,是不能对其进行update or delete 操作,而Gap 锁不锁定记录本身,可以对其进行这些操作。
mysql> desc t;
  +-------+---------+------+-----+---------+-------+
  | Field | Type    | Null | Key | Default | Extra |
  +-------+---------+------+-----+---------+-------+
  | a     | int(11) | NO   | PRI | NULL    |       |
  +-------+---------+------+-----+---------+-------+
  1 row in set (0.00 sec)
  ​
  mysql> insert into t values (10), (20), (30), (40);
  ​
  mysql> begin;
  Query OK, 0 rows affected (0.00 sec)
  ​
  mysql> delete from t where a<= 30;
  Query OK, 3 rows affected (0.00 sec)
  ​
  ---TRANSACTION 3440, ACTIVE 35 sec
  2 lock struct(s), heap size 1136, 3 row lock(s), undo log entries 3
  MySQL thread id 13, OS thread handle 123145543925760, query id 569 localhost root
  TABLE LOCK table `test_mysql`.`t` trx id 3440 lock mode IX
  RECORD LOCKS space id 47 page no 3 n bits 72 index PRIMARY of table `test_mysql`.`t` trx id 3440 lock_mode X locks rec but not gap  # record lock
  Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 32
   0: len 4; hex 8000000a; asc     ;;          # 10
   1: len 6; hex 000000000d70; asc      p;;
   2: len 7; hex 4f000001850110; asc O      ;;
  ​
  Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 32
   0: len 4; hex 80000014; asc     ;;          # 20
   1: len 6; hex 000000000d70; asc      p;;
   2: len 7; hex 4f000001850132; asc O     2;;
  ​
  Record lock, heap no 4 PHYSICAL RECORD: n_fields 3; compact format; info bits 32
   0: len 4; hex 8000001e; asc     ;;          # 30
   1: len 6; hex 000000000d70; asc      p;;
   2: len 7; hex 4f000001850154; asc O     T;;
mysql> select @@global.tx_isolation, @@tx_isolation;
  +-----------------------+-----------------+
  | @@global.tx_isolation | @@tx_isolation  |
  +-----------------------+-----------------+
  | REPEATABLE-READ       | REPEATABLE-READ |
  +-----------------------+-----------------+
  ​
  mysql> select * from t;
  +----+
  | a  |
  +----+
  | 10 |
  | 20 |
  | 30 |
  | 40 |
  +----+
  4 rows in set (0.00 sec)
  ​
  mysql> begin;
  Query OK, 0 rows affected (0.00 sec)
  ​
  mysql> select * from t where a <= 30 for update;
  +----+
  | a  |
  +----+
  | 10 |
  | 20 |
  | 30 |
  +----+
  3 rows in set (0.00 sec)
  ​
  mysql> insert into t select 15;
  ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
  ​
  insert into t select 15
  ------- TRX HAS BEEN WAITING 7 SEC FOR THIS LOCK TO BE GRANTED:
  RECORD LOCKS space id 47 page no 3 n bits 72 index PRIMARY of table `test_mysql`.`t` trx id 3485 lock_mode X locks **gap before rec insert intention waiting**
  Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
   0: len 4; hex 80000014; asc     ;;
   1: len 6; hex 000000000d95; asc       ;;
   2: len 7; hex e40000018f011c; asc        ;;
  ------------------
  TABLE LOCK table `test_mysql`.`t` trx id 3483 lock mode IX RECORD LOCKS space id 47 page no 3 n bits 72 index PRIMAR Y of table `test_mysql`.`t` trx id 3483 lock_mode X
  Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
   0: len 4; hex 8000000a; asc     ;;
   1: len 6; hex 000000000d95; asc       ;;
   2: len 7; hex e40000018f0110; asc        ;;
  ​
  Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compa
  ct format; info bits 0
   0: len 4; hex 80000014; asc     ;;
   1: len 6; hex 000000000d95; asc       ;;
   2: len 7; hex e40000018f011c; asc        ;;
  ​
  Record lock, heap no 4 PHYSICAL RECORD: n_fields 3; compa
  ct format; info bits 0
   0: len 4; hex 8000001e; asc     ;;
   1: len 6; hex 000000000d95; asc       ;;
   2: len 7; hex e40000018f0128; asc       (;;
  ​
  Record lock, heap no 5 PHYSICAL RECORD: n_fields 3; compa
  ct format; info bits 0
   0: len 4; hex 80000028; asc    (;;
   1: len 6; hex 000000000d95; asc       ;;
   2: len 7; hex e40000018f0134; asc       4;;
  ​
  # RR事务隔离级别:游标扫记录,直到第一条不符合要求的记录都加锁

MySQL默认的事务隔离级别(RR)用的是Next-key locking算法

Next-key lock优化成record lock 的条件:

锁定一条记录,锁定的这条记录的索引包含唯一索引

Gap locking is not needed for statements that lock rows using a unique index to search for a unique row. (This does not include the case that the search condition includes only some columns of a multiple-column unique index; in that case, gap locking does occur.)

  RECORD LOCKS space id 47 page no 3 n bits 72 index PRIMAR
  Y of table `test_mysql`.`t` trx id 3487 lock_mode **X locks
   rec but not gap**
  Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compa
  ct format; info bits 0
   0: len 4; hex 80000014; asc     ;;
   1: len 6; hex 000000000d95; asc       ;;
   2: len 7; hex e40000018f011c; asc        ;;

Q:为什么要有隔离性呢?
A: 如果没有隔离性,一个事务del <= 7; 另一个事务在中间 INS 6;

事务 Tx1 Tx2
BEGIN
del <= 10
BEGIN
INS 5
COMMIT
COMMIT

这个表里最后还剩5

但是在log里记录的其实是:

insert

delete

如果从机用这个日志去备份,会有主从不一致

举例

例子:

 对Z表插入了这些数据
  CREATE TABLE z ( a INT, b INT, PRIMARY KEY(a), KEY(b) );
  INSERT INTO z SELECT 1,1;
  INSERT INTO z SELECT 3,1;
  INSERT INTO z SELECT 5,3;
  INSERT INTO z SELECT 7,6;
  INSERT INTO z SELECT 10,8;
  ​
  一个事务执行:
  BEGIN; 
  SELECT * FROM z WHERE b=3 FOR UPDATE;
  ​
  Q: # 在RR隔离级别下,以下哪些能执行,哪些不能执行呢?
  ​
  a: 1  3  5  7  10
  b: 1  1  3  6  8
  ​
  a: 5  X record锁
  b:  (1:3] ,(3,6)  X gap锁
  SELECT * FROM z WHERE a = 5 LOCK IN SHARE MODE;  # 是给a加S锁,前面加了X锁,不行
  INSERT INTO z SELECT 4,2;                        # b=2,在(1,3]范围之内,也不行
  INSERT INTO z SELECT 6,5;                        #不能插入  其实b还有一个GAP锁,范围是(3,6),如果锁是二级索引,且非唯一,那么就会有一个隐含的GAP锁。
  INSERT INTO z SELECT 8,6;                    # OK 6不属于(3,6)返回,会加到原来的记录后面
  INSERT INTO z SELECT 2,0;                     # OK
  INSERT INTO z SELECT 6,7;                    # OK
  ​
  # 如果不锁住(3,6),就可以插入(6,3)  如果再返回记录b=3 ,那返回的就是(5,3),(6,3)

注意:这里的中括号不是数学上的概念,如果新插入的是3,原来是(1,3] 还是能插入3的,是(3,3],它在原范围之后的,不是之内,所以不锁住这个范围,就会有幻读问题。

InnoDB默认使用Next-key lock algorithm,他锁住的是前面记录和当前记录的范围,若锁住了13这条记录,那12这条记录是插入不进来的,这样就解决了幻读问题。

某些情况下,可以将Next-key lock 降解成Record Lock,从而获得并发性。什么情况呢? 当索引含有唯一约束 并且锁定的是一条记录,这时就可以只锁住事务本身。 上面的例子:如果10,11,13,20是PK,意味着unique,如果SELECT PK=10,只锁定一条记录,那它锁住的就是10本身,而不是(-∞,10)这样的范围,但是如果条件是<=10;那锁定的还是 (-∞,10]这样的范围,记录索引是唯一 一条而不是一个范围)的话,锁定1条记录( point select),就不需要使用范围锁了。不管事务是串行还是并行执行的,得到的结果都是一致的。

隔离性越高,持有锁的时间越长,并发性越差。

隔离级别与锁的问题

RR -> next-key lock

RC -> record lock

对于没有索引的表,选一个记录进行删除时。RR 时,锁的是全部记录,RC时,锁的是一条记录。

锁重用

结果:锁开销进一步下降

BEGIN;
  SELECT * FROM t WHERE rowid = xxx FOR UPDATE; 
  SELECT * FROM t WHERE rowid = yyy FOR UPDATE; 
  ​
  BEGIN;
    SELECT * FROM t WHERE rowid = xxx FOR UPDATE;
  SELECT * FROM t WHERE rowid = xxx LOCK IN SHARE MODE; 

隐式锁

开启一个事务,当插入一条记录时候,看不到锁,但是当另一个事务想要对这条记录加锁时就会出现。

Q : 那如何知道这条记录有没有隐式锁呢? 可以通过事务id来判断

所以,在插入的过程中,多个事务可以并行执行。

锁定读:

select * from a where c = 1 for update/lock in share mode

但是一般我们用的是:

select * from a where c = 1

在事务隔离级别<= RR的情况下,这样的读是非锁定读(及时记录被更新,也能被读取,读不会被阻塞,这是通过行多版本来实现的),这行数据是否被更新 可以通过事务id来确认(如果被更新,就读之前的版本)。

死锁

  • 数据库中的死锁有死锁检测机制,可以死锁检测把死锁回滚掉

  • 两个或两个以上的事务在执行过程中 因争夺锁资源而造成的一种互相等待的现象

  • AB-BA

解决死锁

  • 超时 • --innodb_lock_timeout

  • wait-for graph • 自动死锁检测

  • 当数据库检测到死锁时,会自动回滚事务,释放持有的锁。

死锁查看: show engine innodb status\G LATEST DETECTED DEAFLOCK 会显示最近的死锁 5.6有一个参数: innodb_print_all_deadlocks=OFF 把这个参数设置为1,只要发生死锁,就会把死锁信息写到错误文件里面, 这时候,查看错误日志就能看到所有的死锁的信息。

InnoDB锁的算法:

MySQL-lock(锁)-v2.0_第7张图片
image.png

在InnoDB中,锁是通过位图来实现的,用位图来保存哪些记录有锁,保存时不是以记录为单位的,而是根据一个页,一个页中有多少条记录,然后看看这些记录哪些上锁了,在内存中,有这样一个区域,用位图的方式记录一个页里 哪些记录有锁。

你可能感兴趣的:(MySQL-lock(锁)-v2.0)