一、事务的简介

  事务是指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部失败。

  例如:A-->B转账,对应如下的两条SQL语句。

update account set money = money - 500 where name = 'a';
update account set money = money + 500 where name = 'b'

  数据库默认事务是自动提交的,也就是发一条SQL它就执行提交。


二、数据库开启事务命令

  如果想多条SQL放在一个事务中执行,则需要执行下面的命令。(以MySQL为例)


开始事务

start transaction;

回滚事务

rollback;

提交事务

commit;


三、MySQL中使用事务

3.1 创建表

create tableaccount(
   id int primary key auto_increment,
   name varchar(20),
   money double
);

增加数据

insert into accountvalues(null,'aaa',1000);
insert intoaccount values(null,'bbb',1000);
insert intoaccount values(null,'ccc',1000);

wKiom1jVFvPx4ff0AAAUxIH1Cxk070.png

3.2 MySQL中事务是默认自动提交的,每当执行一条SQL,就会提交一个事务(一个SQL就是一个事务)。Oracle中事务是默认不自动提交的,需要在执行SQL语句后,通过commit命令手动提交事务。


3.3 MySQL管理事务

方式一:开启事务管理SQL的语句

开启事务命令

start transaction;

回滚事务:将数据回复语到事务开始时候的状态

rollback;

提交事务:对事务中的进行操作,进行确认操作,事务在提交后,数据就可恢复。

commit;

wKioL1jVGkbih01aAAInD3HLJxg123.png

方式二:数据库中存在了一个自动变量,通过

show variables like '%commit%';

可以查看,如果autocommit的值是on,说明开启自动提交。

wKiom1jVG0GQUEPWAAAipK4_M3c225.png

关闭自动提交,

set autocommit = off;
或
set autocommit = 0;

wKiom1jVG7nws7O_AAAqlBK8QA0423.png

如果甚至autocommit为off,意味着以后每条SQL都会处于一个事务中,相当于每条SQL执行前,都会执行start transaction开启事务命令。

【提示】Oracle中的autocommit默认就是off。


四、JDBC中使用事务

  当JDBC程序向数据库获得一个Connection对象,默认情况下,这个Connection对象会自动向数据库提交在它上面发送的SQL语句。如果想关闭这种默认提交方式,让多条SQL在一个事务中执行,则可以使用下面的语句。


JDBC控制事务语句

connection.setAutoCommit(false);//相当于start transaction
connection.rollback();//相当于rollback
connection.commit();//相当于commit


五、事务的特性(ACID)

☆原子性(Atomicity)

  原子性是指事务是一个不可分割的工作单位,事务中的操作要么全部发生,要么全部失败。

  【提示】:现在物理学证明,夸克是最小的单位,但是在夸克证明之前,人们普遍认为原子是最小的单位,用原子来描述事务,很贴切。

☆一致性(Consistency)

  事务前后数据的完成性必须保持一致。

隔离性(Isolation)

  事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务干扰,多个并发事务之间数据要相互隔离

☆持久性(Durability)

  持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。


六、事务的隔离级别

  多个线程开启各自事务操作数据库中数据的时候,数据库系统要负责隔离操作,以保证各个线程在获取数据时的准确性。


数据库共定义了四种隔离级别:

serializable:可避免脏读、不可重复读、虚读情况的发生。(串行化)

repeatable read:可避免脏读、不可重复读情况的发生(可重复读) ,不可以避免虚读。

read committed:可避免脏读情况发生(读已提交)

read uncommitted:最低解绑,什么也无法保证。(读未提交)


设置事务隔离级别

set session transaction isolation level   xxxx;

查询当前事务隔离级别

select @@tx_isolation;


查看MySQL默认的隔离级别:

wKiom1jVJDPS_coCAAAOxWZnc7Y755.png


  如果不考虑隔离性,可能会引起如下问题:

6.1 脏读

一个事务读取了另一个事务未提交的数据。

这是非常危险的,假设aaa向bbb转账100元,对应SQL语句如下:

update account set money = money - 100 where name = 'aaa';
update account set money = money + 100 where name='bbb';

先设置事务的隔离级别为read uncommited

set session transaction isolation level read uncommitted;

wKioL1jVJkPRFw7aAAAZeLb2rPo652.png


执行的SQL语句如下:

语句1:

update account set money = money + 100 where name = 'bbb';

语句2:

update account set money = money -100 where name = 'aaa';

当第1条SQL执行完毕,第2条还没执行(没提交时),如果此时B查询自己的账户,就会发现自己多个100元,如果A等B走后再回滚,B就会损失100元。


6.2 不可重复读(强调的是update)

  在一个事务内读取表中某一行数据,多次读取结果不同。

  

  例如银行想查询A账户余额,第一次查询A账户为200元,此时A向账户存了100元并提交了。银行接着又进行了一次查询,此时A账户为300元了。银行两次查询不一致,可能就会很困惑,不知道那次查询是准确的。

  

  和脏读的区别是,脏读是读取前一事务未提交的脏数据,不可重复读是重新读取了前一事务已提交的数据。


  很多人认为这种情况就是对的,无需困惑,当然是后面的为准。我们可以考虑到如下的情况,比如银行程序 需要将查询结果分别输出到电脑屏幕和写到文件中,结果在一个事务中针对输出的目的地,进行的两次查询不一致,导致文件和屏幕中的结果不一致,银行工作人员就不知道以哪个为准了。


6.3虚读(幻读)

  一个事务内读取了别的事务插入的数据,导致前后读取不一致。

  

  例如,第一次读取,存在5条记录,然后(另一个事务)向表中插入一条新的记录,第二次读取,存在6条记录。


JDBC程序中能否指定事务的隔离级别 ?

Connection接口中定义事务隔离级别四个常量:

static int TRANSACTION_READ_COMMITTED

          指示不可以发生脏读的常量;不可重复读和虚读可以发生。

static int TRANSACTION_READ_UNCOMMITTED

          指示可以发生脏读 (dirty read)、不可重复读和虚读 (phantom read) 的常量。

static int TRANSACTION_REPEATABLE_READ

          指示不可以发生脏读和不可重复读的常量;虚读可以发生。

static int TRANSACTION_SERIALIZABLE

          指示不可以发生脏读、不可重复读和虚读的常量。

 

通过 void setTransactionIsolation(intlevel) 设置数据库隔离级别


七、事务的丢失更新问题

  两个事务或多个事务更新同一行,但这些事务彼此之间都不知道其他事务进行的修改,因此第二个更改会覆盖了一个修改。

wKiom1jVK2qSByFaAAB2M5L7Jes477.png

丢失更新问题的解决:

悲观锁(假设丢失更新一定会发生)--利用数据库内部锁机制,管理事务


MySQL数据库内部提供两种常用的锁机制:共享锁和排它锁。

允许一张数据表中数据记录,添加多个共享锁,添加共享锁记录,对于其他事务可读不可写。

一张数据表中数据记录,只能添加一个排它锁,在添加排它锁的数据,不能再添加剂其他共享锁和排它锁,对于其他事务是可读不可写的。


所有数据记录修改操作,自动为数据添加排它锁。

添加共享锁方式:select * from account lock in share mode;

添加排它锁方式:select * from account for update;


锁必须在事务中添加,如果事务结束了,锁就释放了。


乐观锁(假设丢失更新不一定会发生)--采用记录的版本×××,来判断记录是否修改过--timestamp

timestamp是可以自动更新的。

create table product(
    id int,
    name varchar(20),
    updatetime timestamp
);
insert into product values(1,'哈哈',null);
update product set name='呵呵' where id = 1;

timestamp在插入和修改的时候,都会自动更新为当前时间。


解决丢失更新,在数据表中添加版本字段,每次修改记录后,版本字段都会更新,如果读取的是版本字段和修改时的版本字段不一致,说明别人进行修改过数据。