Mysql加锁过程详解(3)-select for update/lock in share mode 对事务并发性影响

事务并发性理解

事务并发性,粗略的理解就是单位时间内可以执行的事务数量,常见的单位是 TPS( transactions per second).mysql

那在数据量和业务操做量必定的状况下,常见的提升事务并发性主要考虑的有哪几点呢?程序员

1.提升服务器的处理能力,让事务的处理时间变短。sql

这样不只加快了这个事务的执行时间,也下降了其余等待该事务执行的事务执行时间。数据库

2.尽可能将事务涉及到的 sql 操做语句控制在合理范围,换句话说就是不要让一个事务包含的操做太多或者太少。服务器

在业务繁忙状况下,若是单个事务操做的表或者行数据太多,其余的事务可能都在等待该事务 commit或者 rollback,这样会致使总体上的 TPS 下降。可是,若是每一个 sql 语句都是一个事务也是不太现实的。一来,有些业务自己须要多个sql语句来构成一个事务(好比汇款这种多个表的操做);二来,每一个 sql 都须要commit,若是在 mysql 里 innodb_flush_log_at_trx_commit=1 的状况下,会致使 redo log 的刷新过于频繁,也不利于总体事务数量的提升(IO限制也是须要考虑的重要因素)。并发

3.在操做的时候,尽可能控制锁的粒度,能用小的锁粒度就尽可能用锁的粒度,用完锁资源后要记得当即释放,避免后面的事务等待。post

可是有些状况下,因为业务须要,或者为了保证数据的一致性的时候,必需要增长锁的粒度,这个时候就是下面所说的几种状况。性能

 

select for update 理解

select col from t where where_clause for update 的目的是在执行这个 select 查询语句的时候,会将对应的索引访问条目进行上排他锁(X 锁),也就是说这个语句对应的锁就至关于update带来的效果。spa

那这种语法为何会存在呢?确定是有须要这种方式的存在啦!!请看下面的案例描述:code

 

案例1 

设置事务隔离级别为RR,并关闭自动提交。

在 t1 中,咱们采用普通读:

mysql> select * from amount ;
+----+-------+
| id | money |
+----+-------+
| A  |   100 |
| B  |   600 |
| C  |  1000 |
| D  |  1000 |
+----+-------+
4 rows in set (0.00 sec)

 

而后在 t2 中,咱们更新一条数据:

mysql> update amount set money = 200 where id = 'B';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

  mysql> commit;
  Query OK, 0 rows affected (0.03 sec)

 

此时,因为是RR隔离级别,t1 是察觉不了 t2 的更新状况的,可是,此时id=B的money应为200:

mysql> select * from amount ;
+----+-------+
| id | money |
+----+-------+
| A  |   100 |
| B  |   600 |
| C  |  1000 |
| D  |  1000 |
+----+-------+
4 rows in set (0.00 sec)

 

这时,咱们在 t1 中,将 id=B 的记录加200,咱们发现,这时B的money变成了400,而不是800:

mysql> update amount set money = money + 200 where id = 'B';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from amount ;
+----+-------+
| id | money |
+----+-------+
| A  |   100 |
| B  |   400 |
| C  |  1000 |
| D  |  1000 |
+----+-------+
4 rows in set (0.00 sec)

 

结果是:咱们在 t1 中明明发现B的money是600,加200应该是800,可是实际上确实200+200!。

这在有些业务状况下是不容许的,由于有些业务但愿我经过 select * from lockt; 查询到的数据是此时数据库里面真正存储的最新数据,而且不容许其余的事务来修改只容许我来修改。

 

案例2:

在 t1 中,

mysql> select * from amount where id = 'B' for update;
+----+-------+
| id | money |
+----+-------+
| B  |   400 |
+----+-------+

在 t2 中,更新B的数据时,发现被阻塞了。

由于事务1 的 select * from lockt where id='B' for update; 语句会将 id='B' 这个索引的入口给锁住了,(其实有些时候是范围的索引条目也被锁住了,暂时不讨论。),那么事务2虽然看到了全部的数据,可是想去修改 id='B' 的行数据的时候, 事务1 只能说 “不可能也不容许”。

后面只有事务1 commit或者rollback 之后,事务2 的才可以修改  id='B' 的这个行数据。

总结:

这就是 select for update 的使用场景,为了不本身看到的数据并非数据库存储的最新数据而且看到的数据只能由本身修改,须要用 for update 来限制。

 

select lock in share mode 理解

若是看了前面的 select *** for update ,就能够很好的理解 select lock in share mode ,in share mode 子句的做用就是将查找到的数据加上一个 share 锁,这个就是表示其余的事务只能对这些数据进行简单的select 操做,并不可以进行 DML 操做

那它和 for update 在引用场景上究竟有什么实质上的区别呢?

lock in share mode 没有 for update 那么霸道,因此它有时候也会遇到问题,请看案例3

案例3:

t1,查询全部数据,并在 id = 'B' 记录加了 share 锁:

mysql> select * from amount;
+----+-------+
| id | money |
+----+-------+
| A  |   100 |
| B  |   400 |
| C  |  1000 |
| D  |  1000 |
+----+-------+
4 rows in set (0.00 sec)

mysql> select * from amount where id = 'B' lock in share mode;
+----+-------+
| id | money |
+----+-------+
| B  |   400 |
+----+-------+
1 row in set (0.00 sec)

 

t2,一样的操做:

mysql> select * from amount;
+----+-------+
| id | money |
+----+-------+
| A  |   100 |
| B  |   400 |
| C  |  1000 |
| D  |  1000 |
+----+-------+
4 rows in set (0.00 sec)

mysql> select * from amount where id = 'B' lock in share mode;
+----+-------+
| id | money |
+----+-------+
| B  |   400 |
+----+-------+
1 row in set (0.00 sec)

 

此时,t1 更新 id = 'B' 的记录,发现被阻塞了:

解释:由于事务1 和事务2 都对该行上了一个 share 锁,事务1 觉得就只有本身一我的上了 S 锁,因此当事务一想修改的时候发现无法修改,这种状况下,事务1 须要使用 for update 子句来进行约束了,而不是使用 for share 来使用。

lock in share mode 会出现死锁,当两个事务同时对同一条记录上了share锁,又想更新该数据时,会出现死锁。此时,其中的一方检测的死锁并丢弃,另外一方才能够更新成功。

而 for update 不会出现死锁,由于它是排他锁,只容许一个事务获取该记录上的一把锁。

 

可能用到的情景和对性能的影响

使用情景:

1. select *** for update 的使用场景

为了让本身查到的数据确保是最新数据,而且查到后的数据只容许本身来修改的时候,须要用到 for update 子句。

2. select *** lock in share mode 使用场景

为了确保本身查到的数据没有被其余的事务正在修改,也就是说确保查到的数据是最新的数据,而且不容许其余人来修改数据。可是本身不必定可以修改数据(好比a,b都拿了锁,a更改了数据,由于b还拿着锁,a提交不了,直到超时),由于有可能其余的事务也对这些数据 使用了 in share mode 的方式上了 S 锁。


性能影响:

select for update 语句,至关于一个 update 语句。在业务繁忙的状况下,若是事务没有及时的commit或者rollback 可能会形成其余事务长时间的等待,从而影响数据库的并发使用效率。

select lock in share mode 语句是一个给查找的数据上一个共享锁(S 锁)的功能,它容许其余的事务也对该数据上 S锁,可是不可以容许对该数据进行修改。若是不及时的commit 或者rollback 也可能会形成大量的事务等待。

for update 和 lock in share mode 的区别:前一个上的是排他锁(X 锁),一旦一个事务获取了这个锁,其余的事务是无法在这些数据上执行 for update ;后一个是共享锁,多个事务能够同时的对相同数据执行 lock in share mode。

 

PS:意向锁。

innodb的意向锁有什么做用?
mysql官网上对于意向锁的解释中有这么一句话
“The main purpose of IX and IS locks is to show that someone is locking a row, or going to lock a row in the table.”
意思是说加意向锁的目的是为了代表某个事务正在锁定一行或者将要锁定一行。
①在mysql中有表锁,LOCK TABLE my_tabl_name READ;  用读锁锁表,会阻塞其余事务修改表数据。LOCK TABLE my_table_name WRITe; 用写锁锁表,会阻塞其余事务读和写。
②Innodb引擎又支持行锁,行锁分为共享锁,一个事务对一行的共享只读锁。排它锁,一个事务对一行的排他读写锁。
③这两中类型的锁共存的问题考虑这个例子:
事务A锁住了表中的一行,让这一行只能读,不能写。以后,事务B申请整个表的写锁。若是事务B申请成功,那么理论上它就能修改表中的任意一行,这与A持有的行锁是冲突的。
数据库须要避免这种冲突,就是说要让B的申请被阻塞,直到A释放了行锁。

数据库要怎么判断这个冲突呢?
step1:判断表是否已被其余事务用表锁锁表
step2:判断表中的每一行是否已被行锁锁住。
注意step2,这样的判断方法效率实在不高,由于须要遍历整个表。
因而就有了意向锁。在乎向锁存在的状况下,事务A必须先申请表的意向共享锁,成功后再申请一行的行锁。在乎向锁存在的状况下,
上面的判断能够改为
step1:不变
step2:发现表上有意向共享锁,说明表中有些行被共享行锁锁住了,所以,事务B申请表的写锁会被阻塞。

注意:申请意向锁的动做是数据库完成的,就是说,事务A申请一行的行锁的时候,数据库会自动先开始申请表的意向锁,不须要咱们程序员使用代码来申请。

总结:为了实现多粒度锁机制(白话:为了表锁和行锁都能用)
相关文章
相关标签/搜索