再谈事务

Posted 91洲际哥的笔记

tags:

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

上一篇咱们简单了解了事务,这一篇我们再深入一下吧

Ⅰ、事务隔离级别

事务一共有四种隔离级别

简称 全称 -
ru read-uncommited 未提交读
rc read-commited 提交读
rr repeatable-read 可重复读
srz serializable 可串行

从真正意义上来看只有srz达到真正隔离性的要求

oracel、sqlserver默认rc,mysql默认rr(99.99%达到隔离性要求)

事务隔离级别越低,事务请求的锁越少或者保持锁的时间就越短

1.1 四种隔离级别解读

事务隔离级别解决了三个问题,脏读,不可重复读,幻读

①read-uncommitted

可以读到其他线程未提交的数据,没人用,这就是脏读

②read-committed

解决了脏读,不会读到其他线程未提交的数据

但是a线程事务开始没提交,b线程读不到对应的数据,a线程事务提交后,b读到了

这就是不可重复读,两次读到不一样,破坏了隔离性,一个线程的事务所做的修改被另一个线程可见了

③repeatable-read

解决不可重复读,一边提交了,另一边还是看不到,两次读结果一样

重复读还可以解决一个问题,幻读(读到之前不存在的记录,讲锁的时候再演示)

例外情况:

  • for update锁定读就可以读到第一个线程中事务提交的数据,可以说是幻读,也可以说是一个不可重复读,这就是99.999%,哈哈

  • 这里也是一种不符合隔离性的,读到了之前不存在的记录,只要是支持mvcc就很难做到完全隔离性

④serializable

两阶段加锁可串行化保证隔离性:加锁阶段,只加锁不放锁,解锁阶段,只放锁,不解锁

对于所有的写,每行上面都有锁,会有大量的锁竞争和超时

这样就失去了MVCC特性(非锁定一致性读)

所有操作都要加共享锁,lock in share mode ,执行selct * from xxx;

其实重写为select * from xxx lock in share mode; 对这条记录加共享锁,

强制事务排序,使得不会互相冲突,这样就不存在并发问题,都是串行了,读写相互阻塞

1.2 好奇心,我们对比下rr和sr

RR隔离级别
sesion1:
mysql> set tx_isolation="REPEATABLE-READ";
Query OK, 0 rows affected (0.00 sec)

mysql> create table t_lock(a int, b int, primary key(a));
Query OK, 0 rows affected (0.11 sec)

mysql> insert into t_lock values(1,1);
Query OK, 1 row affected (0.00 sec)

mysql> select * from t_lock;
+---+------+
| a | b    |
+---+------+
| 1 |    1 |
+---+------+
1 row in set (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.02 sec)

mysql> select b from t_lock where a=1;
+------+
| b    |
+------+
|    1 |
+------+
1 row in set (0.00 sec)

session2:
mysql> set tx_isolation="REPEATABLE-READ";
Query OK, 0 rows affected (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update  t_lock set b=2 where a = 1;
Query OK, 1 row affected (0.00 sec) Rows matched: 1  Changed: 1  Warnings: 0

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

session1:
mysql> select b from t_lock where a=1;  -- 再执行一次,得到的结果是1
+------+
| b    |
+------+
|    1 |
+------+
1 row in set (0.00 sec)

mysql> select b from t_lock where a=1 for update;  -- for update的去读,得到的结果是2
+------+
| b    |
+------+
|    2 |
+------+ 1 row in set (0.00 sec)
session1中, RR隔离级别下,前两次读都是读取的快照,最后一次读取的当前更新的值


SR隔离级别
session1:
mysql> select * from t_lock;
+---+------+
| a | b    |
+---+------+
| 1 |    1 |
+---+------+
1 row in set (0.00 sec)

mysql> set tx_isolation=‘SERIALIZABLE‘;
Query OK, 0 rows affected (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select b from t_lock where a=1;
+------+
| b    |
+------+
|    1 |
+------+ 1
row in set (0.00 sec)

session2:
mysql> show engine innodb status\G
-- ------------省略部分输出------------
2 lock struct(s), heap size 360, 1 row lock(s)
MySQL thread id 3, OS thread handle 0x7f946bc94700, query id 30 localhost root cleaning up
TABLE LOCK table `burn_test`.`t_lock` trx id 5390 lock mode IS
RECORD LOCKS space id 15 page no 3 n bits 72 index `PRIMARY` of table `burn_test`.`t_lock` trx id 5390 lock mode S locks rec but not gap  -- 有S锁
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 00000000150c; asc       ;;
2: len 7; hex 8c000000340110; asc     4  ;;
3: len 4; hex 80000001; asc     ;;

mysql> set tx_isolation=‘SERIALIZABLE‘;
Query OK, 0 rows affected (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update t_lock set b=2 where a=1;  -- 在SR的隔离级别下,直接阻塞,因为a=1上有一个S锁
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

SERIALIZABLE的隔离级别一般来说是不会去用

1.3 事务隔离级别的选择

show variables like ‘tx_isolation‘;
set global 后只对之后创建的session生效,之前的session没用

如何查看当前各个线程的事务隔离级别,sys库里面的session表看不了啊?

应该看performance_schema中user_variables_by_thread表

select * from user_variables_by_thread where variable_name = ‘tx_isolation‘;
找到thread_id
select processlist_id from threads where thread_id in(xxx);
再对应到processlist id

重点:

  • 我们生产中一定要用rc,不要用默认的rr,一般场景rc性能会好很多,rc的锁好理解

  • 为了同步数据一致性binlog_format用row,mixed会遇到各种bug导致主从不一致,5.7官方默认用row了,没有协商的余地,组复制也必须用row(目前MySQL若启用rc,不管binlog_format怎么设置都会强行转为row)

    transaction_isolation = read-committed
    binlog_format = row
    上面两个一定要配到my.cnf中,没有任何理由
  • 有一种情况rr比rc好,条件是,应用偏读,一个事务里面有10个select,后面会提到,read_view的原因

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

趁热打铁-再谈分布式事务

再谈数据库事务隔离性

再谈ACID

再谈数据库隔离级别

再谈mysql锁机制及原理—锁的诠释

再谈mysql锁机制及原理—锁的诠释