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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容

  • 当一个系统访问量上来的时候,不只是数据库性能瓶颈问题了,数据库数据安全也会浮现,这时候合理使用数据库锁机制就显得异...
    初来的雨天阅读 3,571评论 0 22
  • 一、概述 数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种...
    忘忧谷主阅读 593评论 0 3
  • 一、概述 数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种...
    不变甄心阅读 2,736评论 0 3
  • 引言 数据库锁定机制简单来说就是数据库为了保证数据的一致性而使各种共享资源在被并发访问变得有序所设计的一种规则;对...
    高级java架构师阅读 724评论 0 1
  • 晚饭过后,随手刷刷朋友圈,翻啊翻...哇!她开的关于生涯规划的培训班,已经是第五期了!这距离上一次的培训结束才半年...
    尤价值阅读 474评论 5 51