锁
概念
对共享资源进行并发访问
保证数据的完整性和一致性
每个数据库的锁的实现是完全不同的。在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锁都会发生等待)
数据库加锁过程是这样的:
如果想对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中,显示:
在另一个进程中,开启一个事务:
未提交的数据,另一个session却可以读到:
脏读 : 能读到未提交事务的状态.
不可重复读:
一个session中:
此时在另一个session中:
此时在第一个session中:
不可重复读:一个事务里执行一条相同的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锁的算法:
在InnoDB中,锁是通过位图来实现的,用位图来保存哪些记录有锁,保存时不是以记录为单位的,而是根据一个页,一个页中有多少条记录,然后看看这些记录哪些上锁了,在内存中,有这样一个区域,用位图的方式记录一个页里 哪些记录有锁。