mysql数据库事务

参考示例图

mysql事务隔离级别和出翔的问题

1.事务及四大特性(ACID)

  • (1)原子性(Atomicity)

事务中所有操作是不可再分割的原子单位。事务中所有操作要么全部执行成功,要么全部执行失败。

  • (2)一致性 (Consistency)

事务执行后,数据库状态与其它业务规则保持一致。如转账业务,无论事务执行成功与否,参与转账的两个账号金额之和应该是不变的。

  • (3)隔离性 (Isolation)

隔离性是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰。也就是说,在事中务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据

  • (4)持久性

一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据。

2.MySQL中的事务

2.1.隐式事务

事务自动开启、提交或回滚,比如insert、update、delete语句,事务的开启、提交或回滚由
mysql内部自动控制的。

mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit   | ON   |
+---------------+-------+
1 row in set, 1 warning (0.00 sec)

autocommit 为ON表示开启了自动提交。

2.2.显示事务

事务需要手动开启、提交或回滚,由开发者自己控制。

  • 方式一
//设置不自动提交事务
set autocommit=0;
//执行事务操作
commit|rollback;
  • 方式二
start transaction;//开启事务
//执行事务操作
commit|rollback;

3.测试回滚和提交

  • 准备测试数据
-- 1、创建数据库jt_db数据库(如果不存在才创建)
create database if not exists jt_db charset utf8;
use jt_db; -- 选择jt_db数据库
-- 2、在 jt_db 库中创建 acc 表(银行账户表), 要求有id(主键),name(姓名),money(账户金额)
drop table if exists acc;
create table acc(
	id int primary key auto_increment,
	name varchar(50),
	money double
);
-- 3、往 acc 表中, 插入2条记录
insert into acc values(null,'A',1000);
insert into acc values(null,'B',1000);
-- 查询acc表中的所有记录
select * from acc;
  • 回滚事务(rollback)
-- 查询acc账户表中A和B的金额
select * from acc;
-- 开启事务
start transaction;
-- 开始转账,A账户减去100元
update acc set money=money-100 where name='A';
-- 查询acc账户表中A和B的金额
select * from acc;
-- B账户增加100元
update acc set money=money+100 where name='B';
-- 查询acc账户表中A和B的金额
select * from acc;
-- 回滚事务
rollback;
-- 再次查询acc账户表中A和B的金额
select * from acc;
  • 提交事务(commit)
-- 将上面的操作再做一次,最后将rollback替换为commit,即提交事务
commit;
  • 中断操作
-- 将上面的操作再做一次,最后将rollback替换为quit,即中断操作
quit;

4.事务的并发问题

多个事务对相同的数据同时进行操作,这叫做事务并发。

三类并发问题

  • (1)脏读 (dirty read):读到另一个事务的未提交更新数据,即读取到了脏数据;
  • (2)不可重复读 (unrepeatable read):对同一记录的两次读取不一致,因为另一事务对该记录做了修改(是针对修改操作)
  • (3)幻读(phantom read):对同一张表的两次查询不一致,因为另一事务插入了一条记录(是针对插入或删除操作);

示例

说明:

mysql默认的是不允许出现脏读和不可重复读,所以在下面演示之前需要设置mysql允许出现脏读、不可重复读等。

set tx_isolation=‘read-uncommitted’;

  • 脏读示例
-- 在窗口1中,开启事务,执行A给B转账100元
set tx_isolation='read-uncommitted'; -- 允许脏读、不可重复读、幻读
use jt_db; -- 选择jt_db库
start transaction; -- 开启事务
update acc set money=money-100 where name='A';
update acc set money=money+100 where name='B';

-- 在窗口2中,开启事务,查询B的账户金额
set tx_isolation='read-uncommitted'; -- 允许脏读、不可重复读、幻读
use jt_db; -- 选择jt_db库
start transaction; -- 开启事务
select * from acc where name='B'; -- 出现脏数据

-- 切换到窗口1,回滚事务,撤销转账操作。
rollback; -- 回滚事务

-- 切换到窗口2,查询B的账户金额
select * from acc where name='B';
在窗口2中,B看到自己的账户增加了100元(此时的数据A操作事务并未提交),此种情况称之为"脏读"。

  • 不可重复读
-- 在窗口1中,开启事务,查询A账户的金额
set tx_isolation='read-uncommitted'; -- 允许脏读、不可重复读、幻读
use jt_db; -- 选择jt_db库
start transaction; -- 开启事务
select * from acc where name='A';

-- 在窗口2中,开启事务,查询A的账户金额减100
set tx_isolation='read-uncommitted'; -- 允许脏读、不可重复读、幻读
use jt_db; -- 选择jt_db库
start transaction; -- 开启事务
update acc set money=money-100 where name='A'; -- A账户减去100
select * from acc where name='A';
commit; -- 提交事务

-- 切换到窗口1,再次查询A账户的金额。
select * from acc where name='A'; -- 前后查询结果不一致
在窗口1中,前后两次对同一数据(账户A的金额)查询结果不一致,是因为在两次查询之间,另一事务对A账户的金额做了修改。此种情况就是"不可以重复读"

  • 幻读示例
-- 在窗口1中,开启事务,查询账户表中是否存在id=3的账户
set tx_isolation='read-uncommitted'; -- 允许脏读、不可重复读、幻读
use jt_db; -- 选择jt_db库
start transaction; -- 开启事务
select * from acc where id=3;

-- 在窗口2中,开启事务,往账户表中插入了一条id为3记录,并提交事务。
-- 设置mysql允许出现脏读、不可重复度、幻读
set tx_isolation='read-uncommitted';
use jt_db; -- 选择jt_db库
start transaction; -- 开启事务
insert into acc values(3, 'C', 1000);
commit; -- 提交事务

-- 切换到窗口1,由于上面窗口1中查询到没有id为3的记录,所以可以插入id为3的记录。
insert into acc values(3, 'C', 1000); -- 插入会失败!
在窗口1中,查询了不存在id为3的记录,所以接下来要执行插入id为3的记录,但是还未执行插入时,另一事务中插入了id为3的记录并提交了事务,所以接下来窗口1中执行插入操作会失败。
探究原因,发现账户表中又有了id为3的记录(感觉像是出现了幻觉)。这种情况称之为"幻读"

解决方案

以上就是在事务并发时常见的三种并发读问题,那么如何防止这些问题的产生?

可以通过设置事务隔离级别进行预防。

5.事务隔离级别

说明:

事务隔离级别分四个等级,在相同数据环境下,对数据执行相同的操作,设置不同的隔离级别,可能导致不同的结果。不同事务隔离级别能够解决的数据并发问题的能力也是不同的。

  • (1)READ UNCOMMITTED(读未提交数据)

    安全级别最低, 可能出现任何事务并发问题(比如脏读、不可以重复读、幻读等)

    性能最好(不使用!!)

  • (2)READ COMMITTED(读已提交数据)(Oracle默认)

    防止脏读,没有处理不可重复读,也没有处理幻读;

    性能比REPEATABLE READ好

  • (3)REPEATABLE READ(可重复读)(MySQL默认)

    防止脏读不可重复读,不能处理幻读问题;

    性能比SERIALIZABLE好

  • (4)SERIALIZABLE(串行化)不会出现任何并发问题,因为它是对同一数据的访问是串行的,非并发访问的;

    性能最差;

说明:

MySQL的默认隔离级别为REPEATABLE READ,即可以防止脏读和不可重复读

6.设置隔离级别

6.1 MySQL设置事务隔离级别(了解)

(1) set tx_isolation=‘read-uncommitted’;

安全性最差,容易出现脏读不可重复读幻觉读,但性能最高

(2) set tx_isolation=‘read-committed’;

安全性一般,可防止脏读,但容易出现不可重复读幻觉读

(3) set tx_isolation=‘repeatable-read’;

安全性较好,可防止脏读不可重复读,但是容易出现幻读

(4) set tx_isolation=‘serialiable’;

安全性最好,可以防止一切事务并发问题,但是性能最差。

6.2 JDBC设置事务隔离界别

JDBC中通过Connection提供的方法设置事务隔离级别:

Connection.setTransactionIsolation(int level)

参数可选值如下:

Connection.TRANSACTION_READ_UNCOMMITTED 1(读未提交数据)

Connection.TRANSACTION_READ_COMMITTED 2(读已提交数据)

Connection.TRANSACTION_REPEATABLE_READ 4(可重复读)

Connection.TRANSACTION_SERIALIZABLE 8(串行化)

Connection.TRANSACTION_NONE 0(不使用事务)

提示:在开发中,一般情况下不需要修改事务隔离级别

Logo

更多推荐