引擎层实现事务。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. 确保单条语句执行时间