Transaction 就是把一些读写操作组成一个逻辑单元,这些操作要么全部执行成功,要么全都不执行,不存在执行一半的情况(造成数据不一致)。数据库完善的事务机制保证了数据的一致性,如事务执行到一半系统宕机,数据库崩溃等各种异常情况下都可以保证数据的一致性,减少了应用程序开发的困难。
begin transaction
read()
write()
。。。
write()
commit (or rollback)
事务的4大特性:
Atomicity(原子性):
这个很好理解,事务是作为一个整体要么全部执行要么回滚,不存在中间状态。跟多线程中的原子操作是一样的。
Consistency(一致性):
这个不是很好理解,可以简单理解为事务可以保证数据的一致性。A给B转账,中间系统崩溃了,A和B的钱都不会丢失。
Isolation(隔离性):
隔离是指在多个事务并发的读写同一数据时各个事务之间要有隔离机制,并发控制不好很容易造成脏读,脏写,更新丢失等各种不一致的情况。主要有 4 种隔离级别。
Durability(持久性):
也很好理解,指事务提交成功后可以保证数据持久化到磁盘,即使事务执行中间数据库崩溃也可以保证。
事务隔离级别:
Read Committed(只能读已提交的数据):
- 当读取时只能读取已经提交的数据。(no dirty reads 防止了脏读 )一个事务不会读取到另外一个未提交事务更新到一半的数据。
- 当写入时只能复写已经提交的数据。(no dirty writes 防止了脏写)一个事务不能复写另外一个未提交事务更新到一半的数据。
实现:
防止脏写一般通过添加行锁来实现,一个事务要更新一行数据时先加行锁,这时其它事务要写时获取不到行锁只能等待。
防止脏读也可以通过锁来实现,读之前必须先获取锁,如果另外一个事务正在写这时读的事务获取不到锁必须等待。但这种方式存在性能问题,因为一个比较耗时的写事务会阻止所有的读事务。所以通常通过同时保存要更新值的旧值和新值来实现,在一个写事务未提交之前,可以读,但只能读取到旧值(在事务开始之前某一时刻照一张照片保存旧值)。
Snapshot Isolation and Repeatable Read (可重复读)
Read Committed 并不能解决所有并发问题。如会出现两次读取不一致的情况,事务1先读取一个值, 之后事务2修改了该值后 commit, 之后事务1再次读取该值发现读取的值改变了,这种不可重复读(两次读取数据不一样)的情况经常会给用户造成困扰。 但这并不违背 Read Committed 的原则, Repeatable Read 可以防止这种情况。
Snapshot Isolation 会存在 Lost update 的情况,两个事务并发的对同一值先读后写,最后写入的结果存在不确定的情况。
一:可以通过显示锁解决更新丢失
BEGIN TRANSACTION;
SELECT * FROM figures
WHERE name = 'robot' AND game_id = 222 FOR UPDATE;
-- Check whether move is valid, then update the position -- of the piece that was returned by the previous SELECT. UPDATE figures
SET position = 'c4' WHERE id = 1234;
COMMIT;
FOR UPDATE 对读取出来的数据上显示锁,这时其它事务不能读也不能写这些查询出来的数据。 这是一种悲观锁,只要认为有可能有并发问题就提前加锁。
二:通过 Compare-and-set 解决更新丢失
UPDATE wiki_pages SET content = 'new content' , version = version + 1 WHERE id = 1234 AND version = 1;
给每一行数据设置一个版本号,先读取,之后提交事务时如果发现版本号变了,说明期间其它事务修改了这个数据,则回滚事务重试。版本号不变则说明没有修改则版本号加1 ,提交成功。
这是一种乐观锁,读取时并不加锁,到提交的时候再判断是否需要重试。适合读比较多的事务,如果写比较多不加锁很容易造成冲突和死锁。
实现: Repeatable Read 一般也通过保存事务执行期间的多个版本的值来实现的,在各个时刻照一张相(所以也叫做 Snapshot Isolation ),在写事务提交前,另外一个事务只能读取到 Snapshot 中的旧值, 防止了两次读取不一致的情况。
Snapshot Isolation 实现较为复杂,不是一两句就能说清楚的。
Serializability(串行化)
之前说 Repeatable Read 不能解决 Lost update (更新丢失) 和 Phantoms (幻读)等情况。 只有 Serializability (串行化)能解决所有问题。 如果让每一个事务一个挨着一个按顺序执行,可以彻底避免并发,这样就不会有任何问题了。(实际上并不是真正的串行执行,但结果和串行执行的结果是一样的)。
既然串行执行这么好,为什么用的较少呢,主要还是效率问题。 串行化一般可以通过加排它锁实现, 读之前对读出来的数据加锁,然后修改, 修改提交后释放锁。这期间其它事务既不能读,也不能写。解决了所有问题。
实际实现一般是 2PL (two-phase locking )已经存在了30 多年。
mysql 默认隔离级别是 Repeatable Read , 所以高并发情况下会出现 Lost update, 幻读, 死锁等各种问题。事务执行时间越长,发生不一致和死锁的概率越高。
本想多写一点,发现很难组织表达清楚,事务这块真不是一两篇文章能说的清楚的,这还不涉及分布式。
网友评论