一、事务及相关特征
1. 事务的概念
事务就是一组原子性的SQL查询,或者说一个独立的工作单元。
如果数据库引擎能够成功地对数据库应用该组查询的全部语句,那么就执行该组查询,如果其中一条语句因为崩溃或其他原因而无法执行,那么该事务中所有的语句都不会执行。
2. 事务的使用
(1)自动提交
开启自动提交是每一条SQL语句都是一个事务,不需要显式地执行事务;关闭自动提交则每一个查询都被当作一个事务执行提交操作,得显式地声明start transaction和commit提交或rollback回滚。MySQL默认采用自动提交模式。
show variables like "autocommit"; set autocommit=0; //0表示AutoCommit关闭 set autocommit=1; //1表示AutoCommit开启
(2)显式使用事务的一般格式
start transaction; select * from emp; //rollback;回滚 commit;
3. 事务的ACID特征
事务必须有ACID特征才可称之为良好的事务处理系统,ACID为原子性(atomiciy)、一致性(consistency)、隔离性(isolation)、持久性(durability)
(1)原子性
一个事务必须为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,不可能只执行其中一部分操作。
(2)一致性
数据库必须总是从一个一致性的状态转换到另外一个一致性的状态。例如,这个一致性是逻辑上而言的,银行的账户存款和他的支出和收入必须满足逻辑条件,不能存款增加了,但是没有任何收入的记录。
(3)隔离性
隔离性是要涉及四个隔离级别,不同的隔离级别下,每一种级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。
(4)持久性
一旦事务提交,则其所做的修改就会永远保存在数据库中,即使系统崩溃,修改的数据也不会丢失。
4. 事务的可能存在的问题(innodb)
在谈事务的隔离级别前,先来谈事物的并发可能会带来的三种问题。
(1)脏读
A事务读取B事务尚未提交的更改数据,并在这个数据的基础上进行操作,这时候如果事务B回滚,那么A事务读到的数据是不被承认的。例如常见的取款事务和转账事务:
(2)不可重复读
不可重复读是指A事务读取了B事务已经提交的更改数据。假如A在取款事务的过程中,B往该账户转账100,A两次读取的余额发生不一致。
(3)幻读
A事务读取B事务提交的新增数据,会引发幻读问题。例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好像发生了幻觉一样。幻读一般发生在计算统计数据的事务中,例如银行系统在同一个事务中两次统计存款账户的总金额,在两次统计中,刚好新增了一个存款账户,存入了100,这时候两次统计的总金额不一致。
注意:不可重复读和幻读的区别是:前者是指读到了已经提交的事务的更改数据(修改或删除),后者是指读到了其他已经提交事务的新增数据。对于这两种问题解决采用不同的办法,防止读到更改数据,只需对操作的数据添加行级锁,防止操作中的数据发生变化;二防止读到新增数据,往往需要添加表级锁,将整张表锁定,防止新增数据(oracle采用多版本数据的方式实现)。
5. 事务的隔离级别
事务的隔离级别是事务的隔离性的级别,不同的隔离级别目的在于解决上述可能出现的问题。注:不同的存储引擎实现的隔离级别不尽相同。
(1)READ UNCOMMITTED(未提交读)
在这种隔离级别下,事务中的修改,即使没有提交,对其他事务也都是可见的。也就是说,事务可以读取未提交的数据,这个问题是脏读。在性能上说,这个隔离级别不会比其他的级别好太多,但是却会出现其他没有出现的问题,所以不是很有必要使用这种隔离级别。
-- ------------------------- read-uncommitted实例 ------------------------------ -- 设置全局系统隔离级别 SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; -- Session A START TRANSACTION; SELECT * FROM USER; UPDATE USER SET NAME="READ UNCOMMITTED"; -- commit; -- Session B SELECT * FROM USER; //SessionB Console 可以看到Session A未提交的事物处理,在另一个Session 中也看到了,这就是所谓的脏读
(2)READ COMMITTED(提交读)
这个隔离级别保证了一个事务如果没有完全提交,那么该事务中的操作是对于其他事务是不可见的。也就是说,这个解决了脏读的问题,但是会出现不可重复读问题。
-- ------------------------- read-cmmitted实例 ------------------------------ -- 设置全局系统隔离级别 SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED; -- Session A START TRANSACTION; SELECT * FROM USER; UPDATE USER SET NAME="READ COMMITTED"; -- COMMIT; -- Session B SELECT * FROM USER; //Console OUTPUT: id name 2 READ UNCOMMITTED 34 READ UNCOMMITTED --------------------------------------------------- -- 当 Session A执行了commit,Session B得到如下结果: id name 2 READ COMMITTED 34 READ COMMITTED
(3)REPEATABLE READ(可重复读)-mysql默认
该级别保证了在同一个事务中多次读取同样的问题,即可以在一个事务中多次执行统一读SQL,返回结果一样。这个隔离级别解决了脏读、不可重复读的问题,但是会有幻读的问题。innodb引擎的多版本并发控制可以解决幻读的问题。
(4)SERIALIZABLE(可串行化)
这是最高的隔离级别,这个级别强制事务串行执行,避免了前面说的幻读的问题。在读取的每一行数据都加上锁,所以不会有任何数据不一致的问题,但是却会导致大量的超时和锁争用的问题。
二、并发控制和锁
1. 并发控制介绍
无论何时,只要有多个查询需要在同一个时间修改数据,都会产生并发控制的问题,上面的脏读不可重复读等均是并发可能产生的问题。此时,我们需要加锁来进行并发控制,保证数据的一致性等。并发控制是一个内容庞大的话题,本文目的在于两个层面的并发控制:服务器层和存储引擎层,只是简要讨论mysql如何实现并发读写控制。
2. 乐观锁和悲观锁(存储引擎层)
3. 死锁和活锁
4. 锁的粒度
5. 多版本并发控制(服务器层)
A事务读取B事务提交的新增数据,会引发幻读问题。幻读一般发生在计算统计数据的事务中,例如银行系统在同一个事务中两次统计存款账户的总金额,在两次统计中,刚好新增了一个存款账户,存入了100,这时候两次统计的总金额不一致。