MySQL(InnoDB剖析):45---事务之(不好的事务习惯:在循环中提交、使用自动提交、使用自动回滚)

一、在循环中提交

  • 开发人员喜欢在循环中进行事务提交,这种方法有两个缺点:出现错误难处理以及效率低

演示案例

  • 创建一个表
create table t1(
    a int not null,
    b varchar(80)
)engine=innodb;

 

  • 下面一个存储过程
delimiter //
create procedure load1(count int unsigned)
begin
    declare s int unsigned default 1;
    declare c char(80) default repeat('a',80);
    while s<=count do
        insert into t1 select NULL,c;
        commit;
        set s=s+1;
    end while;
end;
//
delimiter ;

 

  • 默认情况下,SQL语句都是自动提交的,也就是说在存储过程中,insert语句之后都会有一个隐式的commit操作,因此上面的存储过程也等价于:
delimiter //
create procedure load2(count int unsigned)
begin
    declare s int unsigned default 1;
    declare c char(80) default repeat('a',80);
    while s<=count do
        insert into t1 select NULL,c;
        set s=s+1;
    end while;
end;
//
delimiter ;

 

  • 这两个存储过程有两个问题:
    • ①如果循环执行时发生错误,数据库会停留在一个未知的位置,因此很难进行处理
    • ②性能问题:因为事务每提交一次就需要写一次重做日志,因此效率比较低
  • 现在我们运行上面的两个存储过程,观察它们的运行的时间,可以看到差不多都为两秒多:
call load1(10000);

truncate table t1;

call load2(10000);

 

改进方法①

  • 改进方法就是循环中的所有insert放在一个事务中进行提交,这样的话重做日志只需要写一次,因此效率提高了
delimiter //
create procedure load3(count int unsigned)
begin
    declare s int unsigned default 1;
    declare c char(80) default repeat('a',80);
    start transaction;
    while s<=count do
        insert into t1 select NULL,c;
        set s=s+1;
    end while;
    commit;
end;
//
delimiter ;

 

  • 接着运行这个存储过程,然后观察一下运行的时间,可以看到只运行了0.2秒:
truncate table t1;

call load3(10000);

 

改进方法②

  •  改进方法就是不在存储过程中开启事务,而是在运行存储过程前显式开启一个事务,如下所示:
truncate table t1;

begin;

call load2(10000);

commit;
  • 接着运行这个存储过程,然后观察一下运行的时间,可以看到只运行了0.18秒:

二、使用自动提交

  • 自动提交不是一个好的习惯,因为这会是初级DBA容易犯错,另外还可能使一些开发人员产生错误的理解,如我们在上面介绍到的循环提交问题
  • MySQL数据库默认设置使用自动提交(autocommit),可以使用下面的语句来关闭自动提交功能
set autocommit=0;

 

  • 当然也可以使用start transaction或begin来显式开启一个事务。在显式开启事务之后,在默认设置下(即参数completion_type=0),MySQL会自动执行SET AUTOCOMMIT=0的命,在使用commit或rollback结束一个事务之后自动执行SET AUTOCOMMIT=1

不同编程语言API的自动提交

  • 对于不同语言的API,自动提交也是不同的
  • MySQL C API默认的提交方式是自动提交
  • MySQL Python API则会自动执行SET AUTOCOMMIT=0,禁用自动提交

三、使用自动回滚

  • InnoDB支持通过定义一个HANDLER来进行自动事务的回滚操作,如在一个存储过程中发生了错误会自动对其进行回滚操作

演示案例

  • 因此我发现很多开发人员喜欢在应用程序中使用自动回滚,例如:
  • 创建一个表
create table b(
    a int not null default 0,
    primary key(a)
)engine=innodb default charset=latin1;

  • 存储过程定义了一个exit类型的HANDLER,当捕获到错误时进行回滚:
delimiter //
create procedure sp_auto_rollback_demo()
begin
   declare exit handler for sqlexception rollback;
    start transaction;
    insert into b select 1;
    insert into b select 2;
    insert into b select 1;
    insert into b select 3;
    commit;
end;
//
delimiter ;

 

  • 因为表中a字段为主键,因此第三条insert语句会抛出错误,运行如下,表格没有插入任何数据:

  

  • 但是这个存储过程我们不知道运行的结果是什么,因此我们需要在存储过程中加入一些判断条件,用来查看存储过程的执行结果,更改如下:
-- 发生错误时,先回滚然后返回-1;执行成功返回1
delimiter //
create procedure sp_auto_rollback_demo2()
begin
    declare exit handler for sqlexception begin rollback; select -1; end;
    start transaction;
    insert into b select 1;
    insert into b select 2;
    insert into b select 1;
    insert into b select 3;
    commit;
    select 1;
end;
//
delimiter ;

 

  • 当我们再次运行存储过程时,结果如下,可以看到存储过程返回了-1,存储过程运行失败:

  • 但是问题没有解决,上面虽然在错误时返回-1,但是开发人员不知道自动回滚时发生的是什么错误
  • 习惯使用自动回滚的人大多数是以前使用SQL Server的开发人员。在SQL Server中可以使用SET XABORT ON来自动回滚一个事务。但是SQL Server数据库不仅自动回滚当前的事务,还会抛出异常,开发人员可以捕获这个异常。因此SQL Server和MySQL数据库在这方面是有所不同的
  • 就像前面说的那样,对事务的BEGIN、COMMIT、ROLLBACK操作应该交给程序段来完成,存储过程需要完成的只是一个逻辑的操作,即对逻辑进行封装。下面演示用Python编写的程序调用一个程序过程sp_rollback_demo,这里的存储过程和之前的存储过程sp_auto_rollback_demo在逻辑上完成的内容大致相同:

  • 和sp_auto_rollback_demo不同的是,在sp_rollback_demo中去掉了对事务的控制语句,这些操作交给程序来完成,接下来查看test_demo.py的程序源代码:

  • 运行这个程序:

  • 在程序中控制事务的好处是:用户可以知道错误的原因。例如在这个例子中,我们知道发生了1062这个错误,错误的内容是Duplicate entry '1' for key 'PRIMARY',即发生了主键重复的错误。然后可以根据发生的原因进一步调试程序
发布了1481 篇原创文章 · 获赞 1026 · 访问量 38万+

猜你喜欢

转载自blog.csdn.net/qq_41453285/article/details/104372339
今日推荐