03 | 事务隔离:为什么你改了我还看不见?

引擎层实现事务。MySQL 支持多引擎,MyISAM 不支持事务, InnoDB 支持。

一、隔离性(Isolation)与隔离级别

隔离级别解决:多事务同时执行,脏读、不可重复读(non-repeatable read)、幻读(phantom read)的问题

四种隔离级别https://www.jianshu.com/p/6ed305b67c8c

1、读未提交,事务没提交时,变更被看到。

2、读提交,事务提交,变更才被看到。

3、可重复读,事务执行过程中,启动时看到数据是一致未提交变更对其他事务也是不可见。

4、串行化,对同一行记录,“写”加“写锁”,“读”加“读锁”。冲突时,等前一个完成,才可继续。

表 T 中只有一列,其中一行的值为 1,按照时间顺序执行两个事务的行为。

mysql> create  table T(c int) engine=InnoDB;    insert into T(c)  values(1);

读未提交”, V1 = 2。 B 虽没提交, A 看到了。V2、V3 = 2。

“读提交”,V1 = 1,V2 = 2。B 更新提交后, A 看到。 V3 = 2。

“可重复读”,V1、V2 = 1,V3 = 2。

“串行化”,V1、V2 = 1,V3 = 2。

db里创建视图,访问时候以逻辑结果为准。

    “读未提交”返回记录上最新值没有视图概念;

   “读提交” SQL 语句执行时创建。

    “可重复读”启动时创建(可认为是静态,不受其他事务更新影响),整个事务存在期间都用。。

    “串行化”加锁避免并行

Oracle 默认“读提交”,从 Oracle 迁移到MySQL 的应用,为保证一致, MySQL设置为“读提交”。

配置的方式,启动参数transaction-isolation 设置 READ-COMMITTED

mysql> show  variables like 'transaction_isolation';

| Variable_name |  Value |

|  transaction_isolation | READ-COMMITTED |

可重复读场景呢:

银行月底对账,判断上个月和当前差额,与本月账单明细是否一致。过程中,新交易,不影响结果

二、事务隔离的实现

展开说明“可重复读”。MySQL 更新时记录回滚操作。

从 1 被按顺序改成 2、3、4,回滚日志:

当前值是 4,查询有不同 read-view。同一记录在系统中可多版本(MVCC)。read-view A得到 1,依次执行所有的回滚

即将 4 改成 5,跟A、B、C 会冲突

系统判断,没有事务用回滚日志时(例:没有比这个回滚日志更早 read-view)、删除

尽量不要使用长事务

提交之前,回滚记录保留,占大量存储空间、锁资源,可能拖垮整个库

在 MySQL 5.5 及以前的版本,回滚日志是跟数据字典一起放在ibdata文件里的,即使长事务最终提交,回滚段被清理,文件也不会变小。我见过数据只有 20GB,而回滚段有 200GB 的库。最终只好为了清理回滚段,重建整个库。

三、事务的启动方式

MySQL 的事务启动方式有以下几种:

1.  显式启动事务语句, begin 或 start transaction。配套的提交语句是 commit,回滚语句是 rollback。

2.  set auto commit=0,线程自动提交关掉。执行select 语句,事务启动不会提交。直到 commit 或 rollback或断开连接。

如果是长连接,导致意外长事务。建议 set auto commit=1,显式启动事务。

auto commit 为 1 情况,begin 显式启动,commit 。如果执行 commit work and chain,提交事务并自动启动下一个事务,省去再次begin开销。知道每个语句是否处于事务中

查找持续时间超过 60s 的事务:

select * from  information_schema.innodb_trx where  TIME_TO_SEC (time diff(now(),trx_started) )>60

问题

长事务风险,如何避免

应用开发端来看:

1.  是否set autocommit=0。可在测试环境中,把 MySQL 的 general_log 开起来,然后随便跑一个业务逻辑,通过 general_log 的日志来确认。改成 1。

2.  是否有不必要只读事务。有些框架不管什么先用 begin/commit框起来。我见过有些是业务并没有这个需要,但是也把好几个 select 语句放到了事务中。这种只读事务可以去掉。

3.  业务连接数据库的时候,根据业务本身的预估,通过 SET MAX_EXECUTION_TIME 命令,来控制每个语句执行的最长时间,避免单个语句意外执行太长时间。(为什么会意外?在后续的文章中会提到这类案例)

数据库端来看:

1.  监控information_schema.Innodb_trx 表,设置长事务阈值,超过就报警 / 或者 kill;

2.  Percona 的 pt-kill 这个工具不错

3. 在业务功能测试阶段要求输出所有的 general_log,分析日志行为提前发现问题;

4. 如果使用的是 MySQL 5.6 或者更新版本,把innodb_undo_tablespaces 设置成 2(或更大的值)。如果真的出现大事务导致回滚段过大,这样设置后清理更方便

评论1

SET GLOBAL MAX_EXECUTION_TIME=3000. 确保单条语句执行时间

你可能感兴趣的:(03 | 事务隔离:为什么你改了我还看不见?)