1. 什么是事务
MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你即需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务!
一般来说,事务是必须满足4个条件(ACID): Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性)、Durability(可靠性)
- 原子性
事务必须是原子(不可分割),要么执行成功进入下一个状态,要么失败rollback 到最初状态。 - 一致性
在事务开始之前和事务结束以后,数据库的完整性约束(关联关系的完整性)没有被破坏。 这个一般通过外键来约束。 - 隔离性
一个事务和另外一个事务的执行情况(中间状态)互不影响 - 持久性
在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。
2. 如何在MYSQL 中使用事务
谁可以使用
只有InnoDB /BDB
的之类的transaction_safe table
才能支持。
默认的engineMyISAM
是不支持事务的,show engine
可以看到支持的和默认的engine。可以在[mysqld] 加入:default_storage_engine=InnoDB;
,InnoDB
就是建立表的默认引擎。
建立InnoDB 表:
Create table .... type=InnoDB;
Alter table table_name type=InnoDB;
(如何查看已有表的类型: show create table table_name
)
这样我们就可以在InnoDB
表上进行事务操作了!
如何使用
启动事务的方法分为两种:begin ,rollback,commit .
当然有的人用begin /begin work .推荐用START TRANSACTION 是SQL-99标准启动一个事务。
start transaction;
update from account set money=money-100 where name='a';
update from account set money=money+100 where name='b';
commit;
解释: 这样start transaction 手动开启事务,commit 手动关闭事务。
默认的时候autocommit=1 自动提交是开启的,所以你可以理解为每条语句一输入到mysql就commit 了。你可以这样:
set autocommit=0;
update from account set money=money-100 where name='a';
update from account set money=money+100 where name='b';
commit;
3. 多线程读写存在的问题
脏读:读到的数据不是此刻真实的数据。
脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。不可重复读:读了两次,值不一样。
一个事务对同一行数据重复读取两次,但是却得到了不同的结果。
例如:A事物读取1亿条数据,在读取到第10万的时候,第9万条数据被B事物修改了,此时A并不知晓,A读取完毕,此时A读取的数据和符合A条件的数据并不一致,也就是说按照A条件来读取到的数据,并不是自己真正想要的结果。幻读:原来没有,现在有了…
在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。和不可重复读的区别是:不可重复读是读取到了别人对表中的某一条记录进行了修改,导致前后读取的数据不一致。 幻读是前后读取到表中的记录总数不一样,读取到了其它事务插入的数据。比如现在有 A 和 B 两个应用程序,他们并发访问了数据库中的某一张表,假设表中有 3 条记录,B 执行查询操作, 第一次查询表得到了 3 条记录。此时 A 对表进行了修改,增加了一条记录,当 B 再次查询表的时候,发现多了一条数据。这种情况就造成了 B 的虚读。但是虚读是不一定每次都发生的,这种情况是不确定的。
以上说到了这三种应用事务产生的读写问题,对应的4种隔离级别:
4. 事务隔离级别标准
SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。
Read Uncommitted(读取未提交内容) TRANSACTION_READ_UNCOMMITTED = 1;
允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个数据不允许同时进行写操作,但允许其他事务读此行修改后却没有提交的数据,就是担心人家的事务出问题回滚(ROLLBACK)了,而你还拿这个脏数据继续计算。该隔离级别可以通过“排他写锁”实现。绝大部分的数据库没有二到这个地步。
设置:SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
表现:可以读取任何数据,但是如果更新到同一数据上,需要等待另一个事务执行完,有超时异常:
注意,即使是GLOBAL设置,也需要打开一个新的会话连接,才能生效,包括当前设置连接。如果是SESSION,当前会话马上生效,但绝不会影响到其它会话的隔离级别,使用SELECT @@TX_ISOLATION; 检查一下当前的隔离级别,免得穿越到秦国。任何Mysql设置,首先要清楚它是全局的,还是会话级别的。
Read Committed(读取提交内容) TRANSACTION_READ_COMMITTED = 2;
允许不可重复读取,但不允许脏读取。这可以通过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
设置:SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
表现:对同一数据更新需要等待,一个事务如果没有COMMIT,任何其它事务无法读取它的中间值。因为只是加了行共享锁,所以此时,还是可以读到一个事务里正在被update的数据。这里的问题是一个事务你还可以读另外一个事务正在更新的数据。
Repeatable Read(可重读) TRANSACTION_REPEATABLE_READ = 4;
禁止不可重复读取和脏读取,但是有时可能出现幻影数据,但在innodb中此隔离级别不允许幻象读,应该说这已经是较高级别的安全保证了。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
设置:SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
表现:除了之上两个要求之后,如果一个事务对一行的读取,即使其它事务的的确确已经修改了此项数据,他也还是会将错就错到底,不会去读这条新值,保证一个事务开始后,读取的任何数据都一份
Serializable(可串行化) TRANSACTION_SERIALIZABLE = 8;
与“可重复读取”隔离最大的区别是读也会加锁,另外事务无法更新。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。任何数据的插入与更新,都是慢慢来,象单跑道起飞的飞机一样。在序列化隔离中,innodb会对每一个select语句后自动加上lock in share mode.
设置:SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;
表现:它依然可以同时起多个事务,但是如果对同一数据进行的任何读写操作,都必须一个等待另一个执行完再说,原则是先到的有锁定权。如果你执行一个update,对方也来一个update,那么出现:
在Java中,修改事务隔离级别,void setTransactionIsolation(int level) throws SQLException;
如果出现两边会话设置隔离级别不一致的情况,属性互相独立,以更高隔离级别为准。
参考文献
- share39. Mysql数据库事务详解[EB/OL]. http://socket.blog.163.com/blog/static/2098730042012514102700/.
- wuxiaobo_2009. Mysql 事务(一)[EB/OL]. http://blog.chinaunix.net/uid-22606185-id-3252443.html.