数据库事务
数据库事务(Transaction)是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。一方面,当多个应用程序并发访问数据库时,事务可以在应用程序间提供一个隔离方法,防止互相干扰。另一方面,事务为数据库操作序列提供了一个从失败恢复正常的方法。
它具有以下4个特性(ACID)
(1) 原子性(Atomicity)
事务的原子性是指事务中的操作不可拆分,只允许全部执行或者全部不执行。
(2) 一致性(Consistency)
事务的一致性是指事务的执行不能破坏数据库的一致性,一致性也称为完整性。一个事务在执行后,数据库必须从一个一致性状态转变为另一个一致性状态。
(3) 隔离型(Isolation)
事务的隔离型是指并发的事务相互隔离,不能互相干扰。
(4) 持久性(Durability)
事务的持久性是指事务一旦提交,对数据的状态变更应该被永久保存。
数据库事务隔离级别
数据库事务的隔离级别有4个,由低到高依次为读未提交(Read uncommitted)、读提交(Read committed)、重复读(Repeatable read)、序列化(Serializable),这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。
脏读 | 不可重复读 | 幻读 | |
读未提交(Read uncommitted) | X | X | X |
读提交(Read committed) | O | X | X |
重复读(Repeatable read) | O | O | X |
序列化(Serializable) | O | O | O |
(1) 读未提交(Read uncommitted)/脏读问题
一个事务读到了另一个事务未提交的数据。
背景:给Tom转账500元,但发现转账金额有问题,于是回滚了事务。
例图:
解决方案:
将数据库事务隔离级别提高到“读提交(Read committed)”以上。
现在基本上所有的数据库事务的默认隔离级别都比“读未提交(Read uncommitted)”高。大多数数据库的默认级别就是“读提交(Read committed)”,比如Sql Server、Oracle、Postgres。MySQL是“重复读(Repeatable read)”。
(2) 读提交(Read committed)/不可重复读问题
在同一个事务中如果两次读取相同的数据时,最后的结果却不一致。
背景:Tom在消费,但同时Tom的wife拿Tom的卡在网上购物,并在Tom付款前提交了事物。(Data row的update事件)
例图:
解决方案:乐观锁/行锁
(3) 重复读(Repeatable read)/幻读问题
在同一个事务中如果两次读取相同的数据时,最后的结果数(条数或COUNT数)却不一致。
背景:Tom的wife拿着Tom的信用卡在消费,同时Tom在查询并打印消费记录。Tom在查询和打印消费记录之间, Tom的wife又花了一笔。(Data table的insert事件)
例图:
解决方案:表锁
(4) Serializable 序列化
Serializable 是最高的事务隔离级别,同时代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻像读。
数据库锁
如何加锁,何时加锁,加什么锁,大多是数据库系统根据不同的事务隔离级别自动决定的,当然也可以通过手动设定来满足特定需求。
从实现上分,可以分为乐观锁和悲观锁
乐观锁是一种思想,由程序实现,不会存在死锁等问题。一般通过时间戳或版本号来实现。
悲观锁就是在读取数据的时候,为了不让别人修改自己读取的数据,就会先对自己读取的数据加锁,只有自己把数据读完了,才允许别人修改那部分数据。一般是通过在SQL中加入各种锁语句来实现。
从并发事务锁定的关系上看,可以分为共享锁(读锁/S锁/Share Locks)和排他锁(写锁/独占锁/X锁/Exclusive Locks)
(1) 共享锁与其他锁的兼容性:排斥排他锁(即自己和别的事务不能修改),但不排斥其他共享锁(别的事务也可以读)。
(2) 排他锁与其他锁的兼容性:排他锁不能和其他锁兼容(自己独占资源),如果数据资源上已经加了排他锁,就不能再放置其他的锁了。同样,如果数据资源上已经放置了其他锁,那么也就不能再放置排他锁了。
从锁定的对象不同,可以分为表锁和行锁