1.MySQL参数autocommit生产环境设1还是0?为什么?

2.MySQL参数tx_isolation生产环境上大多数是设什么值,为什么?

3.与MySQL锁相关的有哪些因素?


1.MySQL参数autocommit生产环境设1还是0?为什么?

mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)
  • 查看select @@autocommit;
  • 临时修改set autocommit=0;
  • 自动提交,innodb引擎特有,未提交的数据仅存在buffer中
  • 提交后的数据写入数据文件
  • 使用了begin;,autocommit参数不生效
  • 建议设置为1,并且使用三段式写法begin...dml...commit;
  • select带有metadata lock,如果为0(未提交),会阻塞DDL操作

2.MySQL参数tx_isolation生产环境上大多数是设什么值,为什么?

https://dev.mysql.com/doc/refman/5.6/en/innodb-transaction-isolation-levels.html#isolevel_serializable

mysql> show variables like '%tx_isolation%';
+---------------+-----------------+
| Variable_name | Value |
+---------------+-----------------+
| tx_isolation | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.00 sec)
  • 读未提交 READ-UNCOMMITED
  • 读已提交 READ-COMMITTED
  • 可重复读 REPEATABLE-READ
  • 串行化 SERIALIZABLE
读未提交 READ-UNCOMMITED

一个事务可以读取其他事务未提交的结果

允许脏读、幻读、不可重复读,隔离级别最低,并发最高

实际应用基本不用

  1. A会话设置事务隔离级别为READ-UNCOMMITTED
mysql A>set tx_isolation='READ-UNCOMMITTED';
Query OK, 0 rows affected, 1 warning (0.00 sec) mysql A>select @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)
  1. A/B会话查看相同的表数据,此时表数据一致
mysql A>select * from t1;
+------+------+
| id | name |
+------+------+
| 1 | yzw |
| 2 | yzw2 |
+------+------+
2 rows in set (0.00 sec) mysql B>select * from t1;
+------+------+
| id | name |
+------+------+
| 1 | yzw |
| 2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)
  1. B会话更新记录但不提交
mysql B>start transaction;
Query OK, 0 rows affected (0.00 sec) mysql B>update t1 set name='yzw1' where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0 mysql B>select * from t1;
+------+------+
| id | name |
+------+------+
| 1 | yzw1 |
| 2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)
  1. A会话查看结果
mysql A>select * from t1;
+------+------+
| id | name |
+------+------+
| 1 | yzw1 |
| 2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)

此时A会话读到了B会话开启的未提交的事务

  1. B会话执行rollback
mysql B>rollback;
Query OK, 0 rows affected (0.00 sec) mysql B>
mysql B>select * from t1;
+------+------+
| id | name |
+------+------+
| 1 | yzw |
| 2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)
  1. A会话再查看数据
mysql A>select * from t1;
+------+------+
| id | name |
+------+------+
| 1 | yzw |
| 2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)

B会话回滚后,A会话读到了初始数据

读已提交 READ-COMMITTED
  • 一个事务能读取其他事务已经提交的结果,与上面相反
  • 用得最多,并发高,生产环境使用此级别,
  • oracle/mssql/pg用的是此级别
  • 无法解决不可重复读和幻读
  1. A会话设置事务隔离级别为READ-COMMITTED
mysql A>set tx_isolation='READ-COMMITTED';
Query OK, 0 rows affected, 1 warning (0.00 sec) mysql A>SELECT @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set, 1 warning (0.00 sec)
  1. A/B会话查看相同的表数据,此时表数据一致
mysql A>select * from t1;
+------+------+
| id | name |
+------+------+
| 1 | yzw |
| 2 | yzw2 |
+------+------+
2 rows in set (0.00 sec) mysql B>select * from t1;
+------+------+
| id | name |
+------+------+
| 1 | yzw |
| 2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)
  1. B会话更新记录但不提交
mysql B>start transaction;
Query OK, 0 rows affected (0.00 sec) mysql B>update t1 set name='yzw1' where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0 mysql B>select * from t1;
+------+------+
| id | name |
+------+------+
| 1 | yzw1 |
| 2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)

此时B会话已经看到数据更改

  1. A会话查看结果
mysql A>select * from t1;
+------+------+
| id | name |
+------+------+
| 1 | yzw |
| 2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)

此时A会话无法读到B会话开启的未提交的事务

  1. B会话执行commit;
mysql B>commit;
Query OK, 0 rows affected (0.02 sec) mysql B>select * from t1;
+------+------+
| id | name |
+------+------+
| 1 | yzw1 |
| 2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)
  1. A会话再查看数据
mysql A>select * from t1;
+------+------+
| id | name |
+------+------+
| 1 | yzw1 |
| 2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)

B会话提交后,完成一个事务,A会话读取到B会话做的提交

可重复读 REPEATABLE-READ

可以读取到已提交的结果,mysql默认级别

并发能力比读已提交稍弱,真正的隔离级别,并解决了读已提交无法解决的不可重复读和幻读的问题

与READ_COMMITTED区别是,REPEATABLE-READ开启的情况下,无论其他事务是否提交了新的变更,同一条件的查询返回结果都是一致的

也就是即使数据更新了,原来会话的事务中,相同的查询条件前后查看的结果一样

  1. B会话更新记录但不提交
mysql B>start transaction;
Query OK, 0 rows affected (0.00 sec) mysql B>update t1 set name='yzw1' where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0 mysql B>select * from t1;
+------+------+
| id | name |
+------+------+
| 1 | yzw11|
| 2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)

此时B会话已经看到数据更改

  1. A会话设置事务隔离级别为REPEATABLE-READ
mysql A>set tx_isolation='REPEATABLE-READ';
Query OK, 0 rows affected, 1 warning (0.00 sec) mysql A>SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec) mysql A>start transaction;
Query OK, 0 rows affected (0.00 sec) mysql A>select * from t1;
+------+------+
| id | name |
+------+------+
| 1 | yzw1 |
| 2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)

此时A会话无法读到B会话开启的未提交的事务

  1. B会话执行commit;
mysql B>commit;
Query OK, 0 rows affected (0.02 sec) mysql B>select * from t1;
+------+-------+
| id | name |
+------+-------+
| 1 | yzw11 |
| 2 | yzw2 |
+------+-------+
2 rows in set (0.00 sec)
  1. A会话再查看数据
mysql A>select * from t1;
+------+------+
| id | name |
+------+------+
| 1 | yzw1 |
| 2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)

B会话提交后,完成一个事务,但是在A会话的同一个事务中,还是无法读取到B会话做的提交

  1. A会话此时提交事务
mysql A>commit;
Query OK, 0 rows affected (0.00 sec) mysql A>select * from t1;
+------+-------+
| id | name |
+------+-------+
| 1 | yzw11 |
| 2 | yzw2 |
+------+-------+
2 rows in set (0.00 sec)

A会话提交后,可以读取到B会话做的提交

串行化 SERIALIZABLE

与REPEATABLE-READ相似,只会读取其他事务已经提交的内容

不同的是,如果autocommit为false,select会隐式转化为select ... lock in share mode

  1. B会话更新记录但不提交
mysql B>start transaction;
Query OK, 0 rows affected (0.00 sec) mysql B>update t1 set name='yzw111' where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0 mysql B>select * from t1;
+------+--------+
| id | name |
+------+--------+
| 1 | yzw111 |
| 2 | yzw2 |
+------+--------+
2 rows in set (0.00 sec)

此时B会话已经看到数据更改

  1. A会话设置事务隔离级别为SERIALIZABLE,并且修改autocommit=0,再查看被修改的数据
mysql A>set tx_isolation='SERIALIZABLE';
Query OK, 0 rows affected, 1 warning (0.00 sec) mysql A>SELECT @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE |
+----------------+
1 row in set, 1 warning (0.00 sec) mysql A>set autocommit=0;
Query OK, 0 rows affected (0.00 sec) mysql A>select * from t1;

此时A会话被阻塞,因为隐式加了lock in share mode

  1. B会话执行commit;
mysql B>commit;
Query OK, 0 rows affected (0.02 sec) mysql B>select * from t1;
+------+--------+
| id | name |
+------+--------+
| 1 | yzw111 |
| 2 | yzw2 |
+------+--------+ 2 rows in set (0.00 sec)
  1. A会话再查看数据
mysql A>select * from t1;
+------+--------+
| id | name |
+------+--------+
| 1 | yzw111 |
| 2 | yzw2 |
+------+--------+ 2 rows in set (0.00 sec)
4种隔离级别能解决的并发问题
隔离级别 dirty read(脏读) unrepeatable read(不可重复读) phantom read(幻读)
read uncommitted Y Y Y
read committed X Y Y
reapeatable read X X X
serializable X X X

3.与MySQL锁相关的有哪些因素?

锁 用于多个事务访问同一个对象时根据这些操作访问同一对象的先后次序给事务排序

  • redo和undo解决原子性A
  • undo解决一致性C
  • lock解决隔离性I
  • redo解决次就行D
锁的4种模式
  1. IS-意向共享锁 事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁,给表加锁
  2. IX-意向排它锁 事务打算给数据行加行排它锁,事务在给一个数据行加排它锁前必须先取得该表的IX锁,给表加锁
  3. S-共享锁 允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁,行锁,阻止排它锁
  4. X-排它锁 允许获得排它锁的事务更新数据,阻止其他事务取得相同数据集的共享锁和排他写锁
锁兼容性列表
兼容性 IS S IX X
IS 兼容 兼容 兼容 冲突
S 兼容 兼容 冲突 冲突
IX 兼容 冲突 兼容 冲突
X 冲突 冲突 冲突 冲突
行锁的3种范围
  1. record lock-行记录锁
  2. gap lock-间隙锁 针对普通索引(二级索引)
  3. next-key lock-下一键锁,索引记录锁以及索引记录之间的间隙锁,二者的组合锁
不同数据库锁的实现
DB lock type
innodb 行级锁
oracle 行级锁
myisam 表锁
MSSQL 行级锁、锁升级
lock与latch的区别
type lock latch
对象 事务 线程
保护 数据库对象 内存结构对象
持续时间 长-毫秒级 短-微秒级
模式 表锁行锁 互斥(0/1)
死锁

显式加锁

  • S select * from tabname where ... lock in share mode;
  • X select * from tabname where ... for update;

意向锁是innodb引擎自己增加,不需要干预

update/delete/insert自动加X锁,普通select不加任何锁

共享锁例子
session A session B
myslq A>set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

myslq A>select * from t1 where id=1;
+------+-------+-------+
| id | name1 | name2 |
+------+-------+-------+
| 1  | yzw1    | yzw11   |
+------+-------+-------+
1 row in set (0.00 sec)
mysql B>set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

myslq A>select * from t1 where id=1;
+------+-------+-------+
| id | name1 | name2 |
+------+-------+-------+
| 1  | yzw1    | yzw11   |
+------+-------+-------+
1 row in set (0.00 sec)
session A给ID=1的记录增加S锁:
myslq A>select * from t1 where id=1 lock in share mode;
+------+-------+-------+
| id | name1 | name2 |
+------+-------+-------+
| 1  | yzw1    | yzw11   |
+------+-------+-------+
1 row in set (0.00 sec)
 
  session B进行查询并加S锁:
myslq A>select * from t1 where id=1 lock in share mode;
+------+-------+-------+
| id | name1 | name2 |
+------+-------+-------+
| 1  | yzw1    | yzw11   |
+------+-------+-------+
1 row in set (0.00 sec)
S锁兼容,并不会互相阻塞
session A更新记录,此时给表加IX锁,并给行加X锁:
myslq A>update t1 set name1='YZW1' where id=1;
因为session B此时持有S锁,session A无法加上X锁,因此进入等待X锁
 
  session B也对同样的行进行更新操作:
mysql B>update t1 set name2='YZW11' where id=1;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
检测到死锁退出
session A获得X锁后,更新成功:
Query OK, 1 row affected (1.39 sec)
Rows matched: 1 Changed: 1 Warnings: 0
 
排它锁例子
session A session B
mysql A>set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

mysql A>select * from t1 where id=2;
+------+------+
| id     | name  |
+------+------+
|   2    | yzw2   |
+------+------+
1 row in set (0.00 sec)
mysql A>set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

mysql A>select * from t1 where id=2;
+------+------+
| id     | name  |
+------+------+
|   2    | yzw2   |
+------+------+
1 row in set (0.00 sec)
session A加X锁:
mysql A>select * from t1 where id=2 for update;
+------+------+
| id     | name  |
+------+------+
|   2    | yzw2   |
+------+------+
1 row in set (0.00 sec)
 
  session B可以进行正常查询:
mysql A>select * from t1 where id=2;
+------+------+
| id     | name  |
+------+------+
|   2    | yzw2   |
+------+------+
1 row in set (0.00 sec)
  加S锁被阻塞,锁超时退出:
mysql B>select * from t1 where id=2 lock in share mode;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
  加X锁被阻塞,锁超时退出:
mysql B>select * from t1 where id=2 for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
对锁定记录进行操作并提交,释放锁:
mysql A>update t1 set name='YZW22' where id=2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0

mysql A>commit;
Query OK, 0 rows affected (0.01 sec)
 
  session A释放锁后,session B可以正常加S锁或者X锁,且不会被阻塞


行锁分析
1. 创建测试数据
create table t2(id int,name varchar(10),primary key(id),key(name));
insert into t2 values(1,'A'),(3,'A'),(5,'C'),(7,'G'),(10,'I');
mysql> select * from t2;
+----+------+
| id | name |
+----+------+
| 1 | A |
| 3 | A |
| 5 | C |
| 7 | G |
| 10 | I |
+----+------+
5 rows in set (0.00 sec)
  • 这里的GAP lock一共有6个,1,5,10前后各1个
  • record lock一共有6个,1,3,5,7,10
  • next-key lock,比如1,包含1前面的0和1后面的2,也就是2个间隙

2.测试record lock

测试1

  • session A
mysql A>begin;
Query OK, 0 rows affected (0.00 sec) mysql A>select * from t2 where NAME='C' for update;
+----+------+
| id | name |
+----+------+
| 5 | C |
+----+------+
1 row in set (0.00 sec)

name是普通/二级索引,且是RR级别隔离,最终在主键索引上会给id=5加上record lock,前后加上gap lock

  • session B
mysql B>begin;
Query OK, 0 rows affected (0.00 sec) mysql B>select * from t2 where id=5 lock in share mode;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

session A的X锁和session B的S锁冲突

mysql> select * from information_schema.innodb_locks;
+------------------------+-----------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
| lock_id | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
+------------------------+-----------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
| 421469287688928:45:3:4 | 421469287688928 | S | RECORD | `db1`.`t2` | PRIMARY | 45 | 3 | 4 | 5 |
| 19207:45:3:4 | 19207 | X | RECORD | `db1`.`t2` | PRIMARY | 45 | 3 | 4 | 5 |
+------------------------+-----------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
2 rows in set, 1 warning (0.00 sec)

测试2

  • session A
mysql A>begin;
Query OK, 0 rows affected (0.00 sec) mysql A>select * from t2 where id=5 and name='C' for update;
+----+------+
| id | name |
+----+------+
| 5 | C |
+----+------+
1 row in set (0.00 sec)

记录存在,加X锁,record lock 和前后 gap lock

  • session B
mysql B>begin;
Query OK, 0 rows affected (0.00 sec) mysql B>select * from t2 where id=5 and name='B' for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

记录不存在,位置在C的前面,C前后已经被加了gap lock,因此被阻塞

mysql> select * from information_schema.innodb_locks;
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
| lock_id | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
| 19210:45:3:4 | 19210 | X | RECORD | `db1`.`t2` | PRIMARY | 45 | 3 | 4 | 5 |
| 19209:45:3:4 | 19209 | X | RECORD | `db1`.`t2` | PRIMARY | 45 | 3 | 4 | 5 |
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
2 rows in set, 1 warning (0.00 sec)

3.测试gap lock

测试1

  • session A
mysql A>begin;
Query OK, 0 rows affected (0.00 sec) mysql A>select * from t2 where name='C' for update;
+----+------+
| id | name |
+----+------+
| 5 | C |
+----+------+
1 row in set (4.18 sec)

给ID=5的行加X锁,同时给4/6加间隙锁

  • session B
mysql B>insert into t2 values(4,'C');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql B>insert into t2 values(6,'C');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

5前后已经被加了间隙锁,插入重复的值(4,C)在id=5的前面,(6,C)在id=5的后面,插入被阻塞

mysql> mysql> select * from information_schema.innodb_locks;
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
| lock_id | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
| 19219:45:4:4 | 19219 | X,GAP | RECORD | `db1`.`t2` | name | 45 | 4 | 4 | 'C', 5 |
| 19218:45:4:4 | 19218 | X | RECORD | `db1`.`t2` | name | 45 | 4 | 4 | 'C', 5 |
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
2 rows in set, 1 warning (0.00 sec) mysql> select * from information_schema.innodb_locks;
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
| lock_id | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
| 19219:45:4:5 | 19219 | X,GAP | RECORD | `db1`.`t2` | name | 45 | 4 | 5 | 'G', 7 |
| 19218:45:4:5 | 19218 | X,GAP | RECORD | `db1`.`t2` | name | 45 | 4 | 5 | 'G', 7 |
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
2 rows in set, 1 warning (0.00 sec)

查看谁阻塞谁

mysql> 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;
+----------------+----------------+------------------------------+-----------------+-----------------+----------------+
| waiting_trx_id | waiting_thread | waiting_query | blocking_trx_id | blocking_thread | blocking_query |
+----------------+----------------+------------------------------+-----------------+-----------------+----------------+
| 19219 | 2 | insert into t2 values(6,'C') | 19218 | 3 | NULL |
+----------------+----------------+------------------------------+-----------------+-----------------+----------------+
1 row in set, 1 warning (0.00 sec)

测试2

  • session A
mysql A>begin;
Query OK, 0 rows affected (0.00 sec) mysql A>select * from t2 where name='C' for update;
+----+------+
| id | name |
+----+------+
| 5 | C |
+----+------+
1 row in set (0.00 sec)

主键5加X锁,主键4/6、二级索引B/D/E/F加间隙锁

  • session B
mysql B>begin;
Query OK, 0 rows affected (0.00 sec) mysql B>insert into t2 values(2,'B');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql B>insert into t2 values(8,'F');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql B>insert into t2 values(6,'G');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql B>insert into t2 values(2,'A');
Query OK, 1 row affected (0.00 sec) mysql B>insert into t2 values(8,'D');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql B>insert into t2 values(8,'E');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql B>insert into t2 values(8,'F');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql B>insert into t2 values(11,'D');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

RR(REPEATABLE-READ)模式才会出现这种情况,避免出现幻读



RR模式下有哪些锁测试例子
  1. 创建测试数据
create table t4( \
id int(11) not null default '0', \
user_id int(11) default null, \
user_name varchar(10) default null, \
create_time varchar(10) default null, \
address varchar(10) default null, \
primary key(id), \
key idx_time_name (create_time,user_name) ); insert into t4 values(1,101,'tom','20161010',''), \
(4,104,'joe','20160305',''), \
(6,106,'tom','20161231',''), \
(8,108,'tom','20160515','sh'), \
(10,110,'tom','20160101',''), \
(100,200,'jack','20161120',''); mysql A>select * from t4;
+-----+---------+-----------+-------------+---------+
| id | user_id | user_name | create_time | address |
+-----+---------+-----------+-------------+---------+
| 1 | 101 | tom | 20161010 | |
| 4 | 104 | joe | 20160305 | |
| 6 | 106 | tom | 20161231 | |
| 8 | 108 | tom | 20160515 | sh |
| 10 | 110 | tom | 20160101 | |
| 100 | 200 | jack | 20161120 | |
+-----+---------+-----------+-------------+---------+
6 rows in set (0.00 sec)

id为主键,create_time和user_name为组合索引

select * from t4 \
where create_time > '20160101' \
and create_time < '20161120' \
and user_name='tom' \
and address != '' for update; mysql A>select * from t4 \
-> where create_time > '20160101' \
-> and create_time < '20161120' \
-> and user_name='tom' \
-> and address != '' for update;
+----+---------+-----------+-------------+---------+
| id | user_id | user_name | create_time | address |
+----+---------+-----------+-------------+---------+
| 8 | 108 | tom | 20160515 | sh |
+----+---------+-----------+-------------+---------+
1 row in set (0.00 sec)

最终符合条件的只有1条数据

借用一张图分析

  • 这里索引走的是普通组合索引,因此根据create_time首先定位到3条数据,根据user_name定位到最终1条数据

mysql是聚簇索引,数据存放在主键索引的叶子节点上,二级索引只保存索引结构,最终定位回主键索引

  • 在idx_time_name索引上根据create_time升序排序,给3条数据加了X锁,总共4个gap lock
  • 从二级索引定位到主键索引上的1/4/8三条数据加了X锁,主键索引上不考虑gap lock
MDL锁

metadata lock 用于解决或者保证DDL操作与DML操作之间一致性,在Server级实现

  • session A
mysql A>begin;
Query OK, 0 rows affected (0.00 sec) mysql A>select * from t1;
+------+--------+
| id | name |
+------+--------+
| 1 | yzw111 |
| 2 | YZW22 |
+------+--------+
2 rows in set (0.00 sec)
  • session B
mysql B>begin;
Query OK, 0 rows affected (0.00 sec) mysql B>drop table t1;
  • session C
mysql C>select * from t1;
ERROR 1146 (42S02): Table 'db1.t1' doesn't exist

此时B/C会话阻塞,因为A持有MDL锁,锁模式为shared_read,B持有X锁

一旦Acommit,B成功drop掉表,C报表不存在

解决meta data lock
  • 减少线上DDL操作
  • 线上DB不要随便做alter table;
  • kill掉DDL会话
mysql> show processlist;
+----+------+-----------+------+---------+------+---------------------------------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------+------+---------+------+---------------------------------+------------------+
| 4 | root | localhost | db1 | Query | 16 | Waiting for table metadata lock | drop table t1 |
| 7 | root | localhost | db1 | Sleep | 22 | | NULL |
| 8 | root | localhost | db1 | Query | 12 | Waiting for table metadata lock | select * from t1 |
| 9 | root | localhost | NULL | Query | 0 | starting | show processlist |
+----+------+-----------+------+---------+------+---------------------------------+------------------+
4 rows in set (0.00 sec) mysql> kill 7;
Query OK, 0 rows affected (0.00 sec)
死锁
  • 产生回路 两个或者两个以上的事务执行过程中,分别持有对方需要的锁
  • 加锁顺序不一致 两个或者两个以上事务同一时刻并发执行,因争夺资源而造成的一种互相等待
回路
  • 创建数据
mysql A>create table t1( id int(11) not null default '0', \
-> name varchar(10) default null, \
-> primary key (id));
Query OK, 0 rows affected (0.03 sec) mysql A>insert into t1 values (1,'AAAA'),(2,'BBBB');
Query OK, 2 rows affected (0.10 sec)
Records: 2 Duplicates: 0 Warnings: 0 mysql A>select * from t1;
+----+------+
| id | name |
+----+------+
| 1 | AAAA |
| 2 | BBBB |
+----+------+
2 rows in set (0.00 sec)
session A session B
mysql A>begin;
Query OK, 0 rows affected (0.00 sec)

mysql A>update t1 set name='aaaa' where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
持有一个X锁
 
  mysql B>begin;
Query OK, 0 rows affected (0.00 sec)

mysql B>update t1 set name='bbbb' where id=2;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
也持有一个X锁
mysql A>update t1 set name='CCCC' where id=2;
Query OK, 1 row affected (0.77 sec)
Rows matched: 1 Changed: 1 Warnings: 0
再获得一个X锁
 
  mysql B>update t1 set name='DDDD' where id=1;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
产生死锁
并发

借用一张图

  • 第一个session根据二级索引name查询加X锁,首先给id=1的记录加锁
  • 第二个session根据二级索引age查询加X锁,首先给id=6的记录加锁

此时两个session准备给各自第二条记录加X锁时,发现已经有X锁了,各自进入阻塞状态

减少死锁的方法
  1. 数据库自动检测死锁,优先回滚小事务
  2. innodb_lock_wait_timeout = 3
  3. 尽快提交事务,事务粒度越小越不容易发生死锁
  4. 降低隔离级别,使用RC提高并发降低死锁概率
  5. 多行记录、跨表时,保持事务操作顺序
  6. 优化索引、SQL,减少扫描/锁范围,降低概率
查找锁
  1. show engine innodb status\G;

只能查看最后一个死锁的信息

------------------------
LATEST DETECTED DEADLOCK
------------------------
2018-02-24 01:28:01 0x7f91647b7700
*** (1) TRANSACTION:
TRANSACTION 18793, ACTIVE 38 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 10, OS thread handle 140262433064704, query id 257 localhost root updating
update t1 set name='CCCC' where id=2
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 48 page no 3 n bits 72 index PRIMARY of table `db1`.`t1` trx id 18793 lock_mode X locks rec but not gap waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 4; hex 80000002; asc ;;
1: len 6; hex 00000000496a; asc Ij;;
2: len 7; hex 4f000001a602fb; asc O ;;
3: len 4; hex 62626262; asc bbbb;; *** (2) TRANSACTION:
TRANSACTION 18794, ACTIVE 37 sec starting index read
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 4, OS thread handle 140262432798464, query id 258 localhost root updating
update t1 set name='DDDD' where id=1
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 48 page no 3 n bits 72 index PRIMARY of table `db1`.`t1` trx id 18794 lock_mode X locks rec but not gap
Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 4; hex 80000002; asc ;;
1: len 6; hex 00000000496a; asc Ij;;
2: len 7; hex 4f000001a602fb; asc O ;;
3: len 4; hex 62626262; asc bbbb;; *** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 48 page no 3 n bits 72 index PRIMARY of table `db1`.`t1` trx id 18794 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 4; hex 80000001; asc ;;
1: len 6; hex 000000004969; asc Ii;;
2: len 7; hex 4e00000166035b; asc N f [;;
3: len 4; hex 61616161; asc aaaa;;
  1. 将所有死锁信息打印到日志innodb_print_all_deadlocks
  2. 查看锁

https://dev.mysql.com/doc/refman/5.6/en/innodb-information-schema-examples.html


sql查询谁阻塞谁

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;

查看被阻塞的SQL信息

mysql> select trx_id,trx_state,trx_started,trx_wait_started,trx_mysql_thread_id,trx_query from information_schema.innodb_trx;
+--------+-----------+---------------------+---------------------+---------------------+------------------------------+
| trx_id | trx_state | trx_started | trx_wait_started | trx_mysql_thread_id | trx_query |
+--------+-----------+---------------------+---------------------+---------------------+------------------------------+
| 19219 | LOCK WAIT | 2018-02-25 10:57:44 | 2018-02-25 11:31:39 | 2 | insert into t2 values(6,'C') |
| 19218 | RUNNING | 2018-02-25 10:57:35 | NULL | 3 | NULL |
+--------+-----------+---------------------+---------------------+---------------------+------------------------------+
2 rows in set (0.00 sec)
  • trx_id innodb存储引擎内部唯一的事务ID
  • trx_state 当前事务的状态
  • trx_started 当前事务的开始时间
  • trx_wait_started 事务等待开始的时间
  • trx_mysql_thread_id mysql的线程ID,也就是shop processlist的显示结果
  • trx_query 事务运行的sql语句

查看获取锁类型

mysql> select * from information_schema.innodb_locks;
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
| lock_id | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
| 19219:45:4:5 | 19219 | X,GAP | RECORD | `db1`.`t2` | name | 45 | 4 | 5 | 'G', 7 |
| 19218:45:4:5 | 19218 | X,GAP | RECORD | `db1`.`t2` | name | 45 | 4 | 5 | 'G', 7 |
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
2 rows in set, 1 warning (0.00 sec)
  • lock_id 锁的id
  • lock_trx_id 事务id
  • lock_mode 锁的模式
  • lock_type 锁的类型,是表锁还是行锁
  • lock_table 要加锁的表
  • lock_index 锁的索引
  • lock_space innodb存储引擎表空间的ID号
  • lock_page 被锁住的页的数量,如果是表锁,该值是NULL
  • lock_rec 被锁住的行的数量,如果是表锁,该值是NULL
  • lock_data 被锁住的行的主键值,如果是表锁,该值是NULL

查看申请资源的ID号

mysql> desc information_schema.innodb_lock_waits;
+-------------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+-------------+------+-----+---------+-------+
| requesting_trx_id | varchar(18) | NO | | | |
| requested_lock_id | varchar(81) | NO | | | |
| blocking_trx_id | varchar(18) | NO | | | |
| blocking_lock_id | varchar(81) | NO | | | |
+-------------------+-------------+------+-----+---------+-------+
4 rows in set (0.00 sec) mysql> select * from information_schema.innodb_lock_waits;
+-------------------+-------------------+-----------------+------------------+
| requesting_trx_id | requested_lock_id | blocking_trx_id | blocking_lock_id |
+-------------------+-------------------+-----------------+------------------+
| 19219 | 19219:45:4:5 | 19218 | 19218:45:4:5 |
+-------------------+-------------------+-----------------+------------------+
1 row in set, 1 warning (0.00 sec)
  • requesting_trx_id 申请锁资源的事务ID
  • requesting_lock_id 申请的锁的ID
  • blocking_trx_id 被阻塞的事务ID
  • blocking_lock_id 被阻塞的锁的ID

mysql小白系列_07 锁与事务的更多相关文章

  1. mysql 开发进阶篇系列 6 锁问题(事务与隔离级别介绍)

    一.概述 在数据库中,数据是属于共享资源,为了保证并发访问的一致性,有效性,产生了锁.接下来重点讨论mysql锁机制的特点,常见的锁问题,以及解决mysql锁问题的一些方法或建议. 相比其他数据库,m ...

  2. 深入理解MySQL的并发控制、锁和事务【转】

    本文主要是针对MySQL/InnoDB的并发控制和加锁技术做一个比较深入的剖析,并且对其中涉及到的重要的概念,如多版本并发控制(MVCC),脏读(dirty read),幻读(phantom read ...

  3. mysql小白系列_13 Online DDL

    Online DDL: 一.FIC之前的添加删除二级索引: 1.首先创建临时表,定义目标新表的选项和索引 2.逐行拷贝数据到临时表 3.插入行时更新索引信息 4.数据全部被拷贝到新表后,删除旧表,re ...

  4. mysql小白系列_09 mysql性能优化关键点

    一 服务器参数调优,有哪些关键点? 1. 应用访问优化 优化方法 性能提升效果 优化成本 说明 减少数据访问能不访问就不访问-减少磁盘IO 1~1000 低 缓存服务器缓存mysql数据,Redis. ...

  5. mysql小白系列_06 备份与恢复 - mydumper与myloader

    http://blog.csdn.net/leshami/article/details/46815553 https://www.cnblogs.com/zhoujinyi/p/3423641.ht ...

  6. mysql小白系列_12 sysbench

    压测 1.查看机器负载load top - load average 1m 5m 15m cat /proc/loadavg 与CPU个数有关,1个load表示1个CPUcat /proc/cpuin ...

  7. mysql小白系列_11 MHA

    一.MHA是什么?能干什么的 (1)以Perl语言写的一套Mysql故障切换方案,一个脚本管理工具 (2)保障数据库的高可用性 (3)修复多个slave之间的差异日志,最终使所有的slave保持数据一 ...

  8. mysql小白系列_10 mysql主从复制原理

    1.如何解决主从复制延迟的问题? (1)主从复制原理 http://www.cnblogs.com/jenvid/p/8410922.html 1.salve执行slave start,salve服务 ...

  9. mysql小白系列_04 datablock

    1.为什么创建一个InnoDB表只分配了96K而不是1M? 2.解析第2行记录格式?(用下面的表定义和数据做测试) mysql> create table gyj_t3 (),name2 var ...

随机推荐

  1. QT 无法抓住异常

    出处:https://stackoverflow.com/questions/40980171/qt5core-dll-crashing I've found that enabling /EHa ( ...

  2. 定了,这个vue.js开源项目,面试时,一定会考问

     因为现在的网店,都是用的商城系统, 而实体店都是入座后,扫码打开网上商城进行选购(餐饮,超市等),所以,vue.js迅速开发网上购物商城系统成为了香饽饽, 本人开源2020年4月开发的购物商城系统, ...

  3. javascript SDK开发之webpack打包支持对象展开运算符...

    场景:使用了对象的展开运算符,webpack打包报错,错误如下:Parsing error: Unexpected token - 1.安装依赖 npm install babel-preset-st ...

  4. 初入React源码(一)

    导语 React是我接触的第二个框架,我最初开始接触的是vue,但是并没有深入的理解过vue,然后在工作过程中,我开始使用了React,现在已经觉得React会比vue更加实用,但是这只是个人观点,可 ...

  5. Algorithm Exercises

    汇总一些常见的算法题目,参考代码. 注:部分题目没有合适的oj地址 枚举 Perfect Cubes.Biorhythms.Counterfeit Dollar.EXTENDED LIGHTS OUT ...

  6. 数学--数论--HDU 1299 +POJ 2917 Diophantus of Alexandria (因子个数函数+公式推导)

    Diophantus of Alexandria was an egypt mathematician living in Alexandria. He was one of the first ma ...

  7. 2019年 ICPC亚洲区预赛(上海赛区)总结

    首先,我要说,我输了,输给了自己的无知,输给了自己的心态与实力. 上海区域赛,打铁而归,最终还是没有比过自己SLG的朋友.要说什么呢?实力的差距,还是说给自己的失败找借口?不能进入金牌区,为什么铜牌区 ...

  8. POJ - 3074 Sudoku (搜索)剪枝+位运算优化

    In the game of Sudoku, you are given a large 9 × 9 grid divided into smaller 3 × 3 subgrids. For exa ...

  9. MySQL必知必会1-20章读书笔记

    MySQL备忘 目录 目录 使用MySQL 检索数据 排序检索数据 过滤数据 数据过滤 用通配符进行过滤 用正则表达式进行搜索 创建计算字段 使用数据处理函数 数值处理函数 汇总数据 分组数据 使用子 ...

  10. session与cookie的浅谈

    cookie的用途: 当你浏览网页时,会有一些推送消息,大多数是你最近留意过的同类东西,比如你想买桌子,上淘宝搜了一下,结果连着几天会有各种各样的桌子的链接.这是因为你浏览某个网页的时候,WEB 服务 ...