美文网首页程序员
MySQL-lock(锁)2.0

MySQL-lock(锁)2.0

作者: 一根薯条 | 来源:发表于2018-10-18 23:16 被阅读7次

    概念

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

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

    每个数据库的锁的实现是完全不同的。在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)</pre>
    

    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锁都会发生等待)

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

    加锁

    如果想对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)</pre>
    
    • 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)</pre>
    
    • 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)</pre>
    

    建议用这三张表来查看当前事务的锁。(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)</pre>
    

    遗憾的是在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; </pre>
    
    脏读

    在一个session中,显示:

    image.png

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

    image.png

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


    image.png

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

    不可重复读:

    一个session中:

    image.png

    此时在另一个session中:

    image.png

    此时在第一个session中:

    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;;</pre>
    
    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        ;;</pre>
    

    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锁的算法:

    image.png

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

    相关文章

      网友评论

        本文标题:MySQL-lock(锁)2.0

        本文链接:https://www.haomeiwen.com/subject/qfeezftx.html