事务特性
事务具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
- 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
- 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
- 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
- 持久性(Durability):一个事务一旦提交,他对数据库的修改应该永久保存在数据库中。
事务的隔离级别
由于事务的隔离性允许多个事务同时处理同一数据,所以,在多个事务并发操作的过程中,如果控制不好隔离级别,就有可能产生_脏读_、_不可重复读_或者_幻读_等现象。因此在操作数据的过程中需要合理利用数据库锁机制或者多版本并发控制机制获取更高的隔离等级,但是,随着数据库隔离级别的提高,数据的并发能力也会有所下降。所以,如何在并发性和隔离性之间做一个很好的权衡就成了一个至关重要的问题。
ANSI/ISO SQL定义的标准隔离级别从高到低依次为:可串行化(Serializable)、可重复读(Repeatable reads)、提交读(Read committed)、未提交读(Read uncommitted)。
隔离级别 | 脏读(Dirty Read) | 不可重复读(NonRepeatable Read) | 幻读(Phantom Read) |
---|---|---|---|
未提交读(Read uncommitted) | 可能 | 可能 | 可能 |
提交读(Read committed) | 不可能 | 可能 | 可能 |
可重复读(Repeatable Read) | 不可能 | 不可能 | 可能 |
可串行化(Serializable) | 不可能 | 不可能 | 不可能 |
===========================================================================================
- 未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据。
- 提交读(Read Committed):被读取的数据可以被其他事务修改。这样就可能导致不可重复读。也就是说,事务的读取数据的时候获取读锁,但是读完之后立即释放(不需要等到事务结束),而写锁则是事务提交之后才释放。释放读锁之后,就可能被其他事物修改数据。Oracle等多数数据库默认都是该级别 (不重复读)
- 可重复读(Repeated Read):所有被Select获取的数据都不能被修改,这样就可以避免一个事务前后读取数据不一致的情况。但是却没有办法控制幻读,因为这个时候其他事务不能更改所选的数据,但是可以增加数据,因为前一个事务没有范围锁。Mysql的InnoDB引擎默认隔离级别。
- 可串行化(Serializable):所有事务都一个接一个地串行执行,这样可以避免幻读(phantom reads),每次读都需要获得表级共享锁,读写相互都会阻塞。
数据库并发控制
虽然将事务串形化可以保证数据在多事务并发处理下不存在数据不一致的问题,但串行执行使得数据库的处理性能大幅度地下降,常常是你接受不了的。所以,一般来说,数据库的隔离级别都会设置为read committed(只能读取其他事务已提交的数据),然后由应用程序使用__乐观锁/悲观锁__来弥补数据不一致的问题。
乐观锁
虽然名字中带“锁”,但是乐观锁并不锁住任何东西,而是在提交事务时检查自己上次读取这条记录后,是否有其他事务修改了这条记录,如果没有则提交,如果被修改了则回滚。如果并发的可能性并不大,那么锁定策略带来的性能消耗是非常小的。
常见实现方式:在数据表增加version字段,每次事务开始时将取出version字段值,而后在更新数据的同时version增加1(如:update xxx set data=#{data},version=version+1 where version=#{version}
),如没有数据被更新,那么说明数据由其它的事务进行了更新,此时就可以判断当前事务所操作的历史快照数据。
悲观锁
和乐观锁相比,悲观锁则是一把真正的锁了,它通过SQL语句“select for update”锁住select出的那批数据,这时如果其他事务来更新这批数据时会等待。
总的来说,悲观锁相对乐观锁更安全一些,但是开销也更大,甚至可能出现数据库死锁的情况,建议只在乐观锁无法工作时才使用。
观点仅代表自己,期待你的留言。