MySQL 锁信息和事务

Posted 南鹤-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL 锁信息和事务相关的知识,希望对你有一定的参考价值。

 锁概念

1.1 什么是锁

锁是数据库系统区别于文件系统的一个关键特性。数据库系统使用锁是为了支持对共享资源进行并发访问,提供数据的完整性和一致性。例如:操作缓冲池中的LRU列表,删除、添加、移动LUR列表中的元素。 

对于任何一种数据库来说都需要有相应的锁定机制,所以mysql自然也不能例外。MySQL数据库由于其自身架构的特点,存在多种数据存储引擎,每种存储引擎所针对的应用场景特点都不太一样,为了满足各自特定应用场景的需求,每种存储引擎的锁定机制都是为各自所面对的特定场景而优化设计,所以各存储引擎的锁定机制也有较大区别。MySQL常用存储引擎(MyISAM,InnoDB)用了两种类型(级别)的锁定机制:表级锁定,行级锁定。

读者福利,想要了解python可直接点击链接即可领取相关学习福利包:石墨文档

是安全网站放心,继续访问就可以领取了哦

1、表级锁 
表级别的锁定是MySQL各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。 

 当然,锁定颗粒度大所带来最大的负面影响就是出现锁定资源争用的概率也会最高,致使并大度大打折扣

使用表级锁定的主要是MyISAM,MEMORY,CSV等一些非事务性存储引擎。

2、行级锁 
行级锁定最大的特点就是锁定对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。由于锁定颗粒度很小,所以发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能。 

 虽然能够在并发处理能力上面有较大的优势,但是行级锁定也因此带来了不少弊端。由于锁定资源的颗粒度很小,所以每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大了。此外,行级锁定也最容易发生死锁 

 使用行级锁定的主要是InnoDB存储引擎。

总结如下: 
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低; 
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高;

例如:

 

上面如图只是对myisam 表修改一行记录 ,其他insert 操作 就需要等待上个update 语句执行完成,再执行insert 操作,这时候就会产生表锁。

1.2 InnoDB 锁的类型

InnoDB 存储引擎实现了如下两种标准的行级锁: 
1、共享锁(S Lock),允许事务读一行数据。但不能修改,增加,删除数据。 
2、排他锁 (X Lock),获准排他锁的事务既能读数据,又能修改数据。 

如果一个事务 t1 已近获得了行 r 的共享锁,那么另外的事务 t2 可以获得行 r 的共享锁,因为读取并没有改变行 r 的数据,称这种情况为锁兼容(Lock Compatible)。但若有其他的事务想获得行 r 的排它锁,则必须等待事务t1,t2 释放行 r 的共享锁——这种情况称为锁不兼容(confilict)

此外,InnoDB存储引擎支持多粒度(granular)锁定,这种锁定允许事务在行级上的锁和表级上的锁同时存在。为了支持在不同粒度上进行加锁操作,InnoDB 存储引擎支持了一种额外的锁方式,称为意向锁(Intention Lock)。意向锁是将锁定的对象分为多个层次,意向锁意味着事务希望在更细粒度上进行加锁

 

如上图,若将上锁的对象看成一颗树,那么最下层的对象(行记录)上锁,也就是对最细粒度的对象进行上锁,那么首先需要对粗粒度的对象上锁。如果需要对页上的记录r进行上 X锁,那么分别需要对数据库A、表、页上意向锁,最后对记录r上 X 锁,若其中任何一个部分导致等待,那么该操作需要等待粗粒度锁的完成。举例来说,在对记录r 加 X锁之前,已近有事务对表1 进行了 S 表锁,那么表1 上已存在 S 锁,之后事务需要对记录r 表1 上加 IX , 由于不兼容,所以该事务,需要等待表锁操作的完成。 

 InnoDB 存储引擎支持意向锁设计比较简练,其意向锁即为表级别的锁,设计目的主要是为了在一个事务中揭示下一行将被请求的锁类型。其支持两种意向锁: 

1、意向共享锁( intention shared lock, Is),事务有意向对表中的某些行加共享锁(S锁) 
2、意向排它锁(intention exclusive lock,IX),事务有意向对表中的某些行加排他锁(X锁) 

意向锁是有数据引擎自己维护的,用户无法手动操作意向锁,在为数据行加共享 / 排他锁之前,InooDB 会先获取该数据行所在在数据表的对应意向锁。

由于InnoDB 存储引擎支持的是行级别的锁,因此意向锁其实不会阻塞除全表扫以外的任何请求。

 表级意向锁与行锁的兼容性 
S:共享锁 
X:排它锁 
IS:意向共享锁 
IX:意向排它锁

- IS IX S   X
IS 兼容(compatible) 兼容 兼容 不兼容(conflict)
IX 兼容 兼容 不兼容 不兼容
S 兼容 不兼容 兼容 不兼容
X 不兼容 不兼容 不兼容 不兼容

 

排它锁(X):与任何锁都不兼容。 
共享锁(S):只兼容共享锁和意向共享锁 
意向锁(IS,IX): 互相兼容,行级别的锁只兼容共享锁

1.3 一致性锁定读

用户有时候需要显示地对数据库读取操作进行加锁以保证数据逻辑的一致性。而这要求数据库支持加锁语句,即使是对于select 的只读操作。 InnoDB 存储引擎对于 select 语句支持两种一致性的锁定读 操作:

1
2
select ... for update;
select ... lock in share mode;

  

select … for update 对读取的行记录加一个 X 锁,其他事务不能对已锁定的行加上任何锁。 
select … lock in share mode 对读取的行记录加一个S 锁,其他事务可以向被锁定的加S 锁,但是如果加X锁,则会组赛。 
此外 select ... for update , select ... lock in share mode 必须在一个事务中,当事务提交了,锁也就释放了。因此在使用上诉两句select 锁定语句时,务必加上BEGIN, START TRANSACTION 或者 SET AUTOCOMMIT=0.

 

1.4 一致性非锁定读

在默认的隔离级别下一致读是指InnoDB在多版本控制中在事务的首次读时产生一个镜像,在首次读时间点之前其他事务提交的修改可以读取到,而首次读时间点之后其他事务提交的修改或者是未提交的修改都读取不到

 

唯一例外的情况是在首次读时间点之前的本事务未提交的修改数据可以读取到

在读取提交数据隔离级别下,一致读的每个读取操作都会有自己的镜像

一致读操作不会施加任何的锁,所以就不会阻止其他事务的修改动作

比如最经典的mysqldump --single-transaction备份的时候就是把当前的事务隔离级别改变为可重复读并开启一个一致性事务的快照 , 就是一致性非锁定读


1.5 自增长与锁
一致读在某些DDL语句下不生效: 
1、碰到drop table语句时,由于InnoDB不能使用被drop的表,所以无法实现一致读 
2、碰到alter table语句时,也无法实现一致读 
3、当碰到insert into… select, update … select和create table … select语句时,在默认的事务隔离级别下,语句的执行更类似于在读取提交数据的隔离级别下

自增长在数据库中非常常见的一种属性,也是很多DBA或开发人员首选主键方式。在InnoDB 存储引擎的内存结构中,对每个含有自增长值的表都有一个自增长计数器。

插入操作会依据这个自增长的计数器加1 赋予自增长列。这个实现方式称作AUTO-INC Locking(自增锁)。 这种自增锁是采用一种特殊的表锁机制,为了提高插入的性能,锁不是在一个事务完成后才释放,而是在完成对自增长值插入的sql 语句后立即释放。 
AUTO-INC Locking 从一定程度上提高了并发插入的效率,但还是存在一些性能上的问题。

1、 首先,对于有自增长值的列的并发插入性能较差,事务必须等待前一个插入完成。 
2、其次,对于insert …select 的大数据量的插入会影响插入的性能,因为另一个事务中插入会被阻塞。 
Innodb_autoinc_lock_mode 来控制自增长的模式,改参数的默认值为1

InnoDB提供了一种轻量级互斥量的自增长实现机制,大大提高了自增长值插入的性能。提供参数innodb_autoinc_lock_mode来控制自增长锁使用的算法,默认值为1。他允许你在可预测的自增长值和最大化并发插入操作之间进行权衡。

插入类型的分类:

 插入类型

 说明
 insert-like  指所有的插入语句,例如:insert、replace、insert … select、replace… select、load data
 simple inserts  指再插入前就确定插入行数的语句。例如:insert、replace等。注意:simple inserts不包含 insert … on duplicate key update 这类sql语句
 bulk inserts  指在插入前不能确定得到插入行数的语句,例如:insert … select、 replace … select、load data

 mixed-mode

inserts

 指插入中有一部分的值是自增长的,一部分是确定的。例如:insert into t1(c1, c2) values (1, ‘a’), (NULL, ‘b’), (5, ‘c’), (NULL,’d’); 也可以指 insert … on duplicate key update 这类sql语句

innodb_autoinc_lock_mode 在不同设置下对自增长的影响: 

innodb_autoinc_lock_mode = 0 : MySQL 5.1.22版本之前自增长的实现方式,通过表锁的AUTO-INC Locking方式

innodb_autoinc_lock_mode = 1(默认值): 
对于『simple inserts』,该值会用互斥量(mutex)对内存中的计数器进行累加操作。对于『bulk inserts』会用传统的AUTO-INC Locking方式。这种配置下,如果不考虑回滚,自增长列的增长还是连续的。需要注意的是:如果已经使用AUTO-INC Locking方式去产生自增长的值,而此时需要『simple inserts』操作时,还需要等待AUTO-INC Locking的释放

innodb_autoinc_lock_mode = 2 
对于所有『insert-like』自增长的产生都是通过互斥量,而不是AUTO-INC Locking方式。这是性能最高的方式。但会带来一些问题:

 

因为并发插入的存在,每次插入时,自增长的值是不连续的基于statement-base replication会出现问题 

因此,使用这种方式,任何情况下都需要使用row-base replication,这样才能保证最大并发性能和replication的主从数据的一致 

2 行锁的几种算法

1、Record Lock :单个行记录上的锁 
2、Gap Lock :间隙锁,锁定一个范围,但不包含记录本身 
3、Next-Key Lock:Gap Lock + Record Lock ,锁定一个范围,并且锁定记录本身 
4、Insert Intention Locks:插入意向锁

2.1 Record Lock

Record Lock 总是会去锁住索引记录, 如果InnoDB 存储引擎表在建立的时候没有设置任何一个索引,那么这是InnoDB 存储引擎会使用隐式的主键来进行锁定。 

行级锁是施加在索引行数据上的锁,比如SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE语句是在t.c1=10的索引行上增加锁,来阻止其他事务对对应索引行的insert/update/delete操作。

行锁总是在索引记录上面加锁,即使一张表没有设置任何索引,InnoDB会创建一个隐藏的聚簇索引,然后在这个索引上加上行锁。 

例如:

1
2
3
4
create table t (c1 int primary key);
insert into select 1;
insert into select 3;
insert into select 10;

  

 

1
2
3
4
5
6
7
8
# 会话A
start transaction;
update t set c1=12 where c1 = 10 ;
 
# 会话B:
mysql> update t set c1=11 where c1=10;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
会阻止该事务对索引行上的修改

  

当一个InnoDB表没有任何索引时, 则行级锁会施加在隐含创建的聚簇索引上,所以说当一条sql没有走任何索引时,那么将会在每一条聚集索引后面加X锁,这个类似于表锁,但原理上和表锁应该是完全不同的

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 删除表t的主键索引
alter table t drop primary key;
开启会话1:
start transaction;
update t set c1=11 where c1=10;
 
开启会话2:
start transaction;
update t set c1=8 where c1=10;
这个时候发生了锁等待,
这时候开启会话3,锁等待发生了什么:
mysql> select from sys.innodb_lock_waits\\G;
如下截图吧:

 

2.2 Gap Lock

当 我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件 的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”, InnoDB也会对这个"间隙"加锁

间隔锁是施加在索引记录之间的间隔上的锁, 锁定一个范围的记录、但不包括记录本身,比如SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE语句,尽管有可能对c1字段来说当前表里没有=15的值,但还是会阻止=15的数据的插入操作,是因为间隔锁已经把索引查询范围内的间隔数据也都锁住了 
间隔锁的使用只在部分事务隔离级(可重复读级)别才是生效的 

间隔锁只会阻止其他事务的插入操作,就是只有insert 操作会产生GAP锁, update 操作不会参数GAP 锁

例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 创建keme1 测试数据, 插入模拟数据
create table keme1 (id int primary key,name varchar(10));
insert into keme1 values (1,\'a\'),(3,\'c\'), (4,\'d\'), (5,\'e\'), (6,\'f\');
# 开启三个session 窗口,两个窗口模拟两个事务, 另外一个窗口看 两个事务发生一些间隔锁的信息
session1:
start transaction;
mysql> update keme1 set name=\'bb\' where id between 1 and 3;
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2  Changed: 2  Warnings: 0
 
session2:
start transaction;
mysql> insert into keme1 values (2,\'bb\');
# 这时候就有锁等待了
select from sys.innodb_lock_waits\\G;

使用gap lock的前置条件: 

1、 事务隔离级别为REPEATABLE-READ, innodb_locks_unsafe_for_binlog参数为0,且sql走的索引为非唯一索引(无论是等值检索还是范围检索) 
2 、事务隔离级别为REPEATABLE-READ, innodb_locks_unsafe_for_binlog参数为0,且sql是一个范围的当前读操作,这时即使不是非唯一索引也会加gap lock

Gap Lock 的作用是为了阻止多个事务将记录插入到同一范围内,而这会导致幻读问题的产生。 

可以通过两种方式来关闭Gap Lock: 
1、将事务的隔离级别设置为READ COMMITTED 
2、将参数innodb_locks_unsafe_for_binlog 设置为1

2.3 Next-Key Lock

在默认情况下, mysql的事务隔离级别是可重复读,并且innodb_locks_unsafe_for_binlog 
参数为0,这时默认采用next-key locks。 
所谓Next-Key Locks, 就是记录锁和间隔锁的结合,即除了锁住记录本身,还要再锁住索引之间的间隙。  

 当扫描表的索引时,InnoDB以这种形式实现行级的锁:遇到匹配的的索引记录,在上面加上对应的 S 锁或 X 锁。因此,行级锁实际上是索引记录锁。如果一个事务拥有索引上记录 r 的一个 S 锁或 X 锁,另外的事务无法立即在 r 记录索引顺序之前的间隙上插入一条新的记录。

假设有一个索引包含值:10,11,13和20。下列的间隔上都可能加上一个Next-Key 锁(左开右闭)

 

1
2
3
4
5
(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)

在最后一个区间中,Next-Key锁 锁定了索引中的最大值到 正无穷。 
默认情况下,InnoDB启用 RR 事务隔离级别。此时,InnoDB在查找和扫描索引时会使用Next-Key 锁,其设计的目的是为了解决『幻读』的出现。  

当查询的索引含有唯一(主键索引和唯一索引)属性是,InnoDB 存储引擎会对 Next-Key Lock 进行优化, 将其降级为Record Lock ,即仅锁住索引本身,而不是范围。

2.4 Insert Intention Lock

插入意向锁是一种在数据行插入前设置的gap锁。这种锁用于在多事务插入同一索引间隙时,如果这些事务不是往这段gap的同一位置插入数据,那么就不用互相等待。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
create table keme2 (a int primary key);
insert into keme2 values (10),(11),(13),(20);
 
开启三个会话窗口
session1:
start transaction;
mysql> select from keme2 where a > 18 for update;
+----+
| a  |
+----+
| 20 |
+----+
1 row in set (0.00 sec)
 
 
session2;
start transaction;
mysql> insert into keme2 select 19;

 

 

 

客户端A创建了一个keme2表,包含10,11,13,20四条索引记录,然后去设置一个互斥锁在大于18的所有索引记录上。这个互斥锁包含了在20记录前的gap锁。

 

3 锁问题

通过锁机制可以实现事务的隔离性要求,使得事务可以并发地工作。锁提高了并发,但是也有有潜在的问题。不过好在因为事务隔离性的要求,锁只会带来三种问题,如果可以防止这三种情况的发生,哪将不会产生并发异常。 

3.1 脏读

先了解脏数据,脏页,脏读。 
脏页指的是在缓冲池中已近被修改的页,但是还没有刷新到磁盘中,即数据库实例内存中的页和磁盘中的页数据是不一致的,当然在刷新到磁盘之前,日志都已经被写入到了重做日志文件中。 

脏数据:是指事务对缓冲池中行记录的修改,并且还没有被提交。

对于脏页的读取,是非常正常的。脏页是因为数据库实例内存和磁盘的异步造成的,这并不影响数据的一致性(或者说两者最终会达到一致性,即当脏页都刷到磁盘)。并且因为脏页的刷新是异步的,不影响数据库的可用性,因此可以带来性能的提高。

脏数据是指未提交的数据,如果读到脏数据,即一个事务可以读到另外一个事务中未提交的数据,则显然违反了数据库的隔离性。 

脏读:指的就是在不同的事务下,当前事务可以读到另外事务未提交的数据,简单来说就是可以读到脏数据。

脏读示例:

1
2
create table t (a int primary key);
insert into t values (1);

 

 

- 会话A 会话B
1 set @@tx_isolation=’read-uncommitted’; 
set autocommit=0;
 
2   set @@tx_isolation=’read-uncommitted’;
3   begin;
4   mysql> select * from t\\G; 
a: 1
5 insert into t select 2;  
6   mysql> select * from t\\G;
*** 1. row *** 
a: 1
*** 2. row ***
a: 2

 

会话A 并没有主动提交2这条插入事务 , 但是在会话B 读取到了, 这就是脏读。

3.2 不可重复读

不可重读是在一个事务内读取同一数据集合。在这个事务还没有结束时,另外一个事务也访问同一数据集合,并做了一些DML操作。因此在第一个事务中的两次读取数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的情况,这种情况称为不可重复读。

不可重复读和脏读的区别是:`脏读示读到未提交的数据,而不可重复读读到确实已近提交的数据。

3.3 丢失更新

 虽然数据库能阻止更新问题的产生,但是在生产应用还有另一个逻辑意义丢失更新问题,而导致该问题的并不是因为数据库本身的问题。实际上,在所有多用户计算机系统环境下都有可能产生这个问题。比如下面的情况: 
比如一个用户账号中有10000元,他用两个网上银行的客户端分别进行转账操作,第一次转账9000人民币,因为网络和数据的关系,这时需要等待。但是这时用户操作另一个网上银行客户端,转账1元,如果最终两笔操作都成功了,用户账号的余款是9999 元,第一次转的9000人民币并没有得到更新,但是在转账的另一个账号却会收到这9000元,这导致了结果就是钱变多,而账不平。但是银行了也很聪明啊,个人网银绑定usb key的,不会发生这种情况的。是的,通过usb key 登录也许可以解决这个问题。但是更重要的是在数据库层解决这个问题,避免任何可能发生丢失更新的情况. 
要避免丢失更新发生 ,需要让事务在这种情况下的操作变成串行化,而不是并行的操作。

4 锁阻塞

因为不同锁之间的兼容性关系,在有些时刻一个事务中的锁需要等待另一个事务中的锁释放它所占用的资源,这就是阻塞 。 阻塞并不是一件坏事,其实为了确保事务可以并发正常地运行。 
在InnoDB 存储引擎中,参数innodb_lock_wait_timeout用来控制等待的时间(默认是50秒), innodb_rollback_on_timeout用来设定是否在等待超时时对进行中的事务进行回滚操作(默认是off,不回滚)。参数innodb_lock_wait_timeout可以在mysql 数据库运行时进行调整:

默认情况下InnoDB 存储引擎不会回滚超时引发的错误异常。其实InnoDB 存储引擎在大部分情况下都不会对异常进行回滚。 

 

 

查看锁阻塞的信息:

1
2
3
4
5
6
7
8
9
select from information_schema.innodb_trx\\G; # 查看当前的事务信息
select from information_schema.innodb_locks\\G; # 查看当前的锁信息
select from information_schema.innodb_lock_waits\\G; # 查看当前的锁等待信息
可以联表查,查找自己想要的结果。
select from sys.innodb_lock_waits\\G; # 查看当前的锁等待信息
show engine innodb status\\G;
还可以通过当前执行了执行了什么语句
select from  performance_schema.events_statements_current\\G; 
show full processlist;

  

5 死锁

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

5.1 数据库层面解决死锁的两种方式

1、解决死锁的问题最简单的方式是不要有等待,将任何的等待都转化为回滚,并且事务重新开始。 
这种没有死锁问题的产生。在线上环境中,可能导致并发性能的下降,甚至任何一个事务都不能进行。而这锁带来的问题远比死锁问题更为严重,而这锁带来的问题原题远比死锁问题更为严重,因为这很难被发现并且浪费资源。

2、解决死锁的问题最简单的一种方法时超时,即当两个事务互相等待是,当一个等待时超过设置的某一阈值是,其中一个事务进行回滚,另一个等待的事务就能继续进行。用innodb_lock_wait_timeout用来设置超时的时间。

超时机制虽然简单,仅通过超时后对事务进行回滚的方式来处理,或者说其根据FIFO的顺序选择回滚对象。但若超时的事务所占权重比较大,如事务操作更新很多行(比如某程序猿用死循环来执行一些事务),占用了较多的undo log,这是采用FIFO 的方式,就显得不合适了,因为回滚这个事务的时间相对另一个事务所占用的时间可能会更多。

 

在mysql 5.7.x 和 mysql 5.6.x 对死锁采用的方式: 
mysql 5.6.x 是用锁等待(超时)的方式来解决, 没有自动解决死锁的问题:

 

 

 mysql 5.7.x 默认开启了死锁保护机制:

 

 

5.2 死锁演示

如果程序是串行的,那么不可能发生死锁。死锁只存在于并发的情况,而数据库本身就是一个并发运行的程序,因此可能会发生死锁。

死锁示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
a :创建表
create table temp(id int primary key ,name varchar(10));
insert into temp values(1,\'a\'),(2,\'b\'),(3,\'c\');
此时表里只有3条数据
 
执行步骤根据数据顺序来:
1. 事务1:
start transaction;
update temp set name=\'aa\' where id=1;
 
2. 事务2:
start transaction;
update temp set name=\'bb\' where id=2;
 
 
3. 事务1:update temp set name=\'aaa\' where id=2;
   这时候3的步骤会有锁等待, 立马执行4,就会马上产生死锁
4. 事务2: update temp set name=\'bbb\' where id=1;

  

 

5.3 避免死锁发生的方法

在事务性数据库中,死锁是个经典的问题,但只要发生的频率不高则死锁问题不需要太过担心 
死锁应该非常少发生,若经常发生,则系统是不可用。

查看死锁的方法有两种: 
通过show engine innodb status命令可以查看最后一个死锁的情况 
通过innodb_print_all_deadlocks参数配置可以将所有死锁的信息都打印到MySQL的错误日志中

减少死锁发生的方法: 

1、尽可能的保持事务小型化,减少事务执行的时间可以减少发生影响的概率 
2、及时执行commit或者rollback,来尽快的释放锁 
3、当要访问多个表数据或者要访问相同表的不同行集合时,尽可能的保证每次访问的顺序是相同的。比如可以将多个语句封装在存储过程中,通过调用同一个存储过程的方法可以减少死锁的发生 
4、增加合适的索引以便语句执行所扫描的数据范围足够小 
5、尽可能的少使用锁,比如如果可以承担幻读的情况,则直接使用select语句,而不要使用select…for update语句 
6、如果没有其他更好的选择,则可以通过施加表级锁将事务执行串行化,最大限度的限制死锁发生

6 事务

事务的主要目的了:事务会把数据库从一种一致状态转换为另一种一致状态。在数据库提交工作是,可以确保要么所有修改都已近保存了,要么所有修改都不保存。

InnoDB 存储引擎中的事务完全符合ACID 的特性。 
原子性 (atomicity) 
一致性(consistency) 
隔离性(isolation) 
持久性(durability)

6.1 了解事务

事务可由一条非常简单的sql 语句组成,也可以有一组复杂的sql 组成。事务是访问并更新数据库中各种数据项的一个程序执行单元,在事务中的操作,要么都做修改,要么都不做这就是事务的目的。 
事务ACID 的特性

6.1.1 原子性

A (Atomicity),原子性。指整个数据库事务是不可分割的工作单位。只有使事务中所有的数据库操作都执行成功,才算整个事务成功。事务中任何一个SQL 语句执行失败,已近执行 
成功的sql 语句也必须撤销。数据库状态应该退回到执行事务前的状态。 

比如ATM 取款流程: 
1、登录ATM 机平台,验证密码。 
2、从远程银行数据库中,取得账户的信息。 
3、用户在ATM 输入提取的金额。 
4、从远程银行的数据库中,更新账户信息。 
5、ATM 机出款。 
6、用户取钱。 
整个过程都视为原子操作,某一个步骤失败了, 都不能进行下一步。

 

6.1.2 一致性

C (consistency),一致性。一致性定义基本可以理解为是事务对数据完整性约束的遵循。这些约束可能包括主键约束、外键约束或是一些用户自定义约束。事务执行的前后都是合法的数据状态,不会违背任何的数据完整性,这就是“一致”的意思。事务是一致性的单位,如果事务中某个动作失败了,系统就可以自动撤销事务——返回事务初始化的状态。

6.1.3 隔离性

I (isolation),隔离性。隔离性还有其他的称呼,如并发控制,可串行化,锁 等。事务的隔离性要求每个读写事务的对象对其他事务的操作对象能相互分离,即该事务提交前对其他事务都不可见。

6.1.4 持久性

D(durability),持久性。事务一旦提交,其结果就是永久性的(写入了磁盘),即使发生宕机等故障,数据库也能将数据恢复。需要注意的是,只能从事务本身的角度来保证结果的永久性。 

 

例如:在事务提交后,所有的变化都是永久的,即使当数据库因为崩溃而需要恢复时,也能保证恢复后提交的数据都不会丢失。但若不是数据库本身发生故障,而是一些外部的原因,如RAID卡损坏,自然灾害等原因导致数据库发生问题,那么所有提交的数据可能都会丢失。因此持久性保证事务系统的高可靠性,而不是高可用性。对于高可用性的实现,事务本身并不能保证,需要一些系统来共同配合来完成。

6.2 事务的实现

事务的隔离性由锁来实现。原子性,一致性,持久性通过数据库的redo log 和undo log 来完成,redo log 成为重做日志,用来保证事务的原子性和持久性。 undo log 用来保证事务的一致性。

redo 和 undo 的作用都可以视为是一种恢复操作,redo 恢复提交事务修改的页操作,而undo 回滚行记录到某个特定版本。因此两者记录的内容不同,redo 通常是物理日志,记录的是页的物理修改操作,undo 是逻辑日志,根据每行记录进行记录。

6.2.1 redo

重做日志(redo log)用来实现事务的持久性,即事务ACID 中的 D。 其中两部分组成: 一是内存中的重做日志缓冲(redo log buffer),其实容易丢失的;二是重做日志文件(redo log file),其是持久的。

InnoDB 是事务的存储引擎,其通过Force Log at Commit 机制实现事务的持久性,即当事务提交(commit)时,必须先将该事务的所有日志写入到重做日志文件进行持久化,待事务的commit 操作完成才算完成。这里的日志是指重做日志,在InnoDB 存储引擎中,由两部分组成,即redo log 和 undo log 。redo log 用来保证事务的持久性,undo log 用来帮助事务回滚及多版本控制(mvcc)的功能,redo log 基本上都是顺序写的,在数据库运行不需要对redo log 的文件进行读取操作。而undo log 是需要进行随机读写的。

为了确保每次日志都写入重做日志文件,在每次都将重做日志缓冲写入重做日志文件后,InnoDB存储引擎都需要调用一次fsync 操作。由于重做日志文件打开并没有使用O_DIRECT选项,因此重做日志缓冲先写入文件系统缓冲。为了确保重做日志写入磁盘,必须进行一次fsync 操作。由于fsync 的效率取决于磁盘的性能,因此磁盘的性能决定了事务的提交的性能,也就是数据库的性能

InnoDB 存储引擎允许用户手工非持久性的情况发生,以此提高数据库的性能。 
即当事务提交时,日志不写入重做日志文件,而是等待一个时间周期后再执行fsync 操作。 

InnoDB 存储引擎允许用户手工非持久性的情况发生,以此提高数据库的性能。 

即当事务提交时,日志不写入重做日志文件,而是等待一个时间周期后再执行fsync 操作。

用参数 innodb_flush_log_at_trx_commit用来控制重做日志刷新到磁盘的策略。该参数默认值为1 

改参数可以设置值为 0、1、2

0 : 表示事务提交时不进行写入重做日志操作,这个操作仅在master thread 中完成,而在master thread 中每1秒会进行一次重做日志的fsync 操作。

1 :  表示每个事务提交时进行写入到重做日志。

2 :  表示事务提交时将重做日志写入重做日志文件,但仅写入文件系统的缓存中,不进行fsync操作。 在这个设置下,当mysql 数据库发生宕机(就是数据库服务意外停止)而操作系统不发生宕机是,不会导致事务的丢失。而当操作系统宕机时,重启数据库后会丢失未从文件系统缓存刷新到重做日志文件那部分事务。

6.2.2 undo

1 、基本概念 
重做日志记录了事务的行为,可以很好地通过其对页进行”重做”操作,但是事务有时还需要进行回滚操作,这时就需要undo。 因此在对数据库进行修改时,InnoDB 存储引擎不但会产生redo,还会产生一定量的undo。这样如果用户执行的事务或语句由于原因失败了,又或者用户用一条rollback 语句请求回滚,就可以利用这些undo 信息将数据回滚到修改之前的样子。

redo 存放在重做日志文件中,与redo 不同,undo 存放在数据库内部的一个特殊段(segment)中,这个段称为undo 段 。undo 段位于共享表空间内。

undo 是 逻辑日志,因此只是将数据库逻辑地恢复到原来的样子。所有修改都被逻辑地取消了,但是数据结构和页本身在回滚之后可能大不相同。 这是因为在多用户并发系统中,可能会有数十,数百甚至数千个并发事务。数据库的主要任务就是协调对数据记录的并发访问。比如,一个事务在修改当前一个页中某几条记录,同时还有别的事务在对同一个页中另几条记录进行修改。因此,不能将一个页回滚到事务开始的样子,因为这样会影响其他事务正在进行的工作。

 undo 除了回滚操作,undo 的另一个作用是mvcc,即在InnoDB 存储引擎中mvcc 的实现是通过undo 来完成。当用户读取一行记录时,若该记录已近被其他事务占用,当前事务可以通过undo 读取之前的行版本信息,以此实现 非锁定读取。

最重要的一点是,undo log 会产生redo log ,也就是undo log 的产生会伴随着redo log 的产生,这是因为u

以上是关于MySQL 锁信息和事务的主要内容,如果未能解决你的问题,请参考以下文章

mysql 啥时候开启行级锁

查看锁信息(开启InnoDB监控)

MySQL锁

mysql行锁等待异常

mysql事务锁

实战篇:如何查看mysql里面的锁