概述
通常的观念认为,事务仅与数据库相关。
将一堆的SQL语句绑定在一起执行,结果是要么全都执行成功(都成功才算成功),要么全都执行失败(有一个失败就按全失败来处理)!
事务的例子
转账,张三(1000元)给李四(1000元)转账100元
开启事务
张三账户减去100元:
update 账户表 set money=money-100 where name='张三';
--900
李四账户上加100元:
update 账户表 set money=money+100 where name='李四';
--1000
结束事务 提交/回滚
特性
(1)原子性
事务中的所有操作都是一个整体,不能被分割,要么全部执行成功,要么全都执行失败。
(2)一致性
在事务执行前后的业务数据之和,是保持一致的。
举例
转账,张三(1000元)给李四(1000元)转账100元。
开启事务
张三(1000元)-100元=900元
李四(1000元)+100元=1100元
结束事务:提交/回滚
不管事务最后是提交了,还是回滚了,张三和李四的账户金额之和,在转账前,转账后是保持一致的!
(3)隔离性
在事务并发时,多个事务之间是具有隔离性的,在一个事务中看不到另一个事务正在进行的数据状态。
(4)持久性
事务提交后,在事务中对数据的更新会持久的保存到数据库中。
在事务提交之前,事务中执行的操作,并没有真正的更新到数据库。
举例
开始事务
张三(1000元)给李四(1000元)转账500元
张三(1000元)-500元=500元
李四(1000元)-500元=1500元
结束事务:提交/回滚
在事务提交执行,张三减去500元,以及李四加上500元,其实并没有真正修改张三和李四的账户金额,只是在日志中记录了,将来如果事务提交了,在事务提交后,才会真正的刀数据库中,更新张三账户金额为500元,更新李四账户金额为1500元!
MySQL中的事务
1.默认情况下,MySQL中的每一条SQL都是一个事务!
2.每次在执行SQL语句之前,会默认开启事务,在执行这条SQL语句之后,会自动提交事务。
3.如果需要让两条或者两条以上的SQL语句在一个事务中执行,需要再执行之前开启事务,在最后一条SQL语句执行完成后,立即结束事务。
开启事务:start transaction;
结束事务:commit(提交)/rollback(回滚);
如果事务汇总的所有SQL语句都执行成功了,最后我们可以提交事务;
如果事务中的SQL语句有一条执行失败,就按全部失败来处理,最后我们会回滚(撤销)事务;
示例
演示A账户给B账户转账
(1)开启事务
start transaction
(2)A账户减去100元
update acc set money=money-100 where name='A'; -- 900元
(3)B账户加上100元
upadate acc set money+100 where name='B'; -- 1100元
(4)查询A,B的账户金额(在不同的cmd窗口进行查看)
select * from acc;
(5)将事务回滚(撤销),将事务提交
rollback;/commit;
并发读问题
多个事务对相同数据进行操作,这叫做事务并发。
在事务并发时,如果没有采取必要的隔离措施,可能会导致各种并发问题,破坏数据的完整性等。这些问题中,其中三类是读问题,分别是:脏读,不可重复读,幻读。
(1)脏读
在一个事务中,读取到另外一个事务为提交更新的数据,称之为脏读。
举例
A(买家)B(卖家)
A转账给B 100元
开启事务:
A-100=900元
B+100=1100元
此时没有提交事务。。。
B在另外一个事务中查询到账户增加了100元,也就是1100元。由于MySQL默认不允许出现脏读,所以要想演示脏读现象,必须将mysql的事务隔离级别设置为最低。
set tx_isolation = 'read-uncommitted';
(2)不可重复读
在一个事务中对同一数据的两次查询结果不一致,是因为另一个事务对该数据进行了修改操作!
举例
事务一:在事务中查询A账户的余额
第一次查询:select * from acc where name='A'; -- 1000
第二次查询:select * from acc where name='A'; -- 900
事务二:A给B转账100元
update acc set money = money-100 where name 'A'
...
提交事务
(3)幻读
在一个事务中对同一张表的两次查询结果不一致,是因为有另一个事务对表进行了插入或者删除操作!
举例
事务一:
第一次:select * from acc where id=3;不存在
插入一条id为3的记录:insert into acc value(2,'C',2000);--插入失败
第二次:select * from acc where id=3;--已存在
事务二:
插入了一条id为3的记录:insert into acc value(3,'C',3000);
提交了事务
隔离级别
read uncommitted(读未提交数据)
安全性最差,不能防止任何的并发读问题,但性能最高,不推荐使用。
red committed(读已提交数据)
Oracle默认的隔离级别
安全性比'读未提交'好,可以防止脏读,但不能防止不可重复读,也不能防止幻读,性能比'读未提交'差。
repeatable read(可重复读)
MySQL默认的隔离级别
安全性比'读已提交'好,可以防止脏读,不可重复读,但不能防止幻读,性能比'读已提交'差。
由于mysql默认的隔离级别不允许出现脏读和不可重复读,因此要想要在mysql中演示这两个现象,必须要设置mysql的隔离级别为最低级别,也就是'读未提交'。
serializable(串行化)
安全性最好,可以防止脏读,不可重复读,幻读,性能最差,也不推荐使用!
设置隔离级别
因为数据库有自己默认的事务隔离级别,我们不需要去设置
mysql中设置事务的隔离级别
查询当前使用的事务隔离级别:
select @@tx_isolation
设置事务的隔离级别:
set tx_isolation='read-uncommitted' 设置隔离级别为'读未提交'
set tx_isolation='read-conmmitted' 设置隔离级别为'读已提交'
set tx_isolation='repeatable-read' 设置隔离级别为'可以重复读'
set tx_isolation='serialiable' 设置隔离级别为'串行化'
JDBC中设置事务的隔离级别
JDBC中通过Connection提供的方法设置事务隔离级别:
Connection.setTransactionIsolation(int level)
level参数可选值如下
Connection.TRANSACTION_READ_UNCOMMITTED 1(读未提交数据)
Connection.TRANSACTION_READ_COMMITTED 2(读已提交数据)
Connection.TRANSACTION_REPEATABLE_READ 4(可重复读)
Connection.TRANSACTION_SERIALIZABLE 8(串行化)
Connection.TRANSACTION_NONE 0(不使用事务)
JDBC执行事务
JDBC也是在执行SQL语句之前,默认开启事务,执行之后,立即提交事务!
因此想要在JDBC中,将两条或者以上的SQL语句放在一个事务中执行,需要关闭自动提交事务!
conn.setAutoCommit(false);
小结
事务就是一组原子操作单元,从数据库的角度说,就是一组SQL指令,要么全都执行成功,若因为某个原因其中一条指令执行有错误,则撤销先前执行过的所有指令。更简答的说就是:要么全部执行成功,要么撤销不执行。