事务
MySQL 事务主要用于处理操作量大,复杂度高的数据。
比如:在人员管理系统中,你删除一个人员,你即需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务!
特性
一般来说,事务是必须满足4个条件(ACID):原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。
- 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
- 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
- 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
隔离级别
ANSI SQL标准定义的四个隔离级别为:
- READ UNCOMMITTED(未提交读),事务中的修改,即使没有提交,在其他事务也都是可见的。事务可以读取未提交的数据,这也被称为脏读。
- READ COMMITTED(提交读),一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。这个级别有时候也叫做不可重复读,因为两次执行相同的查询,可能会得到不一样的结果。因为在这2次读之间可能有其他事务更改这个数据,每次读到的数据都是已经提交的。
- REPEATABLE READ(可重复读),解决了脏读,也保证了在同一个事务中多次读取同样记录的结果是一致的。但是理论上,可重读读隔离级别还是无法解决另外一个幻读的问题,指的是当某个事务在读取某个范围内的记录时,另外一个事务也在该范围内插入了新的记录,当之前的事务再次读取该范围内的记录时,会产生幻行。
- SERIALIZABLE(可串行化),它通过强制事务串行执行,避免了前面说的幻读的问题。
几个可能产生的错误:
- 脏读(dirty read):一个事务可以读取另一个尚未提交事务的修改数据。
- 不可重复读(nonrepeatable read):在同一个事务中,同一个查询在T1时间读取某一行,在T2时间重新读取这一行时候,这一行的数据已经发生修改,可能被更新了(update),也可能被删除了(delete)。
- 幻读(phantom read):在同一事务中,同一查询多次进行时候,由于其他插入操作(insert)的事务提交,导致每次返回不同的结果集。
InnoDB采用MVCC来支持高并发,并实现了四个标准的隔离级别。其默认级别是REPEATABLE READ(可重复读),并且通过间隙锁(next-key locking)策略防止幻读的出现。间隙锁使得InnoDB不仅仅锁定查询涉及的行,还会对索引中的间隙进行锁定,以防止幻影的插入。 隔离级别越低,事务请求的锁越少或保持锁的时间就越短。所以很多数据库系统默认的事务隔离级别是READ COMMITTED。质疑SERIALIZABLE隔离级别的性能,但是InnoDB存储引擎认为两者的开销是一样的,所以默认隔离级别使用REPEATABLE READ。
用命令设置当前会话或全局会话的事务隔离级别。
SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL
{
READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE
}
//配置文件
[mysqld]
transaction-isolation = READ-COMMITTED
//查看事务级别
select @@tx_ioslation; //当前
select @@global.tx_isolation; //全局
MYSQL处理事务隔离(MVCC)
重点:主要是将表锁降低到了行锁。
Multi-Version Concurrency Control 多版本并发控制,MVCC 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问;在编程语言中实现事务内存。
英文好难: Concurrency [kən'kʌrəns] Con curren cy
MVCC会保存某个时间点上的数据快照。这意味着事务可以看到一个一致的数据视图,不管他们需要跑多久。这同时也意味着不同的事务在同一个时间点看到的同一个表的数据可能是不同的。
通过为每一行记录添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。但是InnoDB并不存储这些事件发生时的实际时间,相反它只存储这些事件发生时的系统版本号。这是一个随着事务的创建而不断增长的数字。每个事务在事务开始时会记录它自己的系统版本号。每个查询必须去检查每行数据的版本号与事务的版本号是否相同。让我们来看看当隔离级别是REPEATABLE READ时这种策略是如何应用到特定的操作的:
SELECT InnoDB必须每行数据来保证它符合两个条件:
- InnoDB必须找到一个行的版本,它至少要和事务的版本一样老(也即它的版本号不大于事务的版本号)。这保证了不管是事务开始之前,或者事务创建时,或者修改了这行数据的时候,这行数据是存在的。
- 这行数据的删除版本必须是未定义的或者比事务版本要大。这可以保证在事务开始之前这行数据没有被删除。这里的不是真正的删除数据,而是标志出来的删除。真正意义的删除是在commit的时候。
符合这两个条件的行可能会被当作查询结果而返回。
- INSERT:InnoDB为这个新行记录当前的系统版本号。
- DELETE:InnoDB将当前的系统版本号设置为这一行的删除ID。
- UPDATE:InnoDB会写一个这行数据的新拷贝,这个拷贝的版本为当前的系统版本号。它同时也会将这个版本号写到旧行的删除版本里。
这种额外的记录所带来的结果就是对于大多数查询来说根本就不需要获得一个锁。他们只是简单地以最快的速度来读取数据,确保只选择符合条件的行。这个方案的缺点在于存储引擎必须为每一行存储更多的数据,做更多的检查工作,处理更多的善后操作。
MVCC只工作在REPEATABLE READ和READ COMMITED隔离级别下。READ UNCOMMITED不是MVCC兼容的,因为查询不能找到适合他们事务版本的行版本;它们每次都只能读到最新的版本。SERIABLABLE也不与MVCC兼容,因为读操作会锁定他们返回的每一行数据 [1] 。
个人理解: 当锁发生时,创建数据副本,副本的版本 = 事务的版本。
模拟一个数据结构
struct data {
lock Lock,
value {
line data_value,
version int,
next *data,
},
lockversion map[int]int
}
当事务开启时,data1 = clone data; data.next = data1; data.version += 1; data.locked;
当事务结束时, data.next != null data = data.next; (要释放之前的data); data.unlocked;
lockversionmap 对应的是一个map 事务ID对应数据版本ID。
当锁为其他级别时,
- RUC级别 读原始数据 不产生副本。
- RC级别 读到可能已经修改的数据,产生副本。
- RR级别 如果多次读的话,并行有其他事务,可能结果不一致。
- 当锁为串行锁的时候,一旦发现locked,就必须等待锁结束,才会读取数据。
补充
- SQL规范所规定的标准,不同的数据库具体的实现可能会有些差异
- mysql中默认事务隔离级别是可重复读时并不会锁住读取到的行
- 事务隔离级别为读提交时,写数据只会锁住相应的行
- 事务隔离级别为可重复读时,如果有索引(包括主键索引)的时候,以索引列为条件更新数据,会存在间隙锁间隙锁、行锁、下一键锁的问题,从而锁住一些行;如果没有索引,更新数据时会锁住整张表。
- 事务隔离级别为串行化时,读写数据都会锁住整张表
- 隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大,鱼和熊掌不可兼得啊。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed,它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。