为什么需要事务
其实和我们需要锁
是一个道理。因为我们业务上的一个逻辑操作单元Tx_A,对应的是多个计算机指令。我们需要一种机制来确保在Tx_A操作执行期间,其内部状态不可以被外界观测到。如果外界访问,要么阻塞等待直到该操作结束。要么直接返回告诉它,“Sorry ,I’m busy now”。事务就是这样一种机制。做数据库的大神说,“得了,您也别自己去实现这样一种机制了,就我实现得了。你们会用就行,免得一大堆bug”,所以事务的出现使得我们能更好focus在我们的业务上。而不用自己去实现事务等价一套APIs。
事务是一个说明性的概念,这个概念用来表述你想要达到什么样的效果。
Mysql对外暴露的事务APIs如下:
- 1.事务的开始
- START TRANSACTION
- BEGIN
- 2.事务提交
- COMMIT
- 3.事务回滚
- ROLLBACK
ANSI/ISO isolation(隔离级别)
一言以蔽之,隔离级别是事务之间可见性的定义。
1. 可序列化(Serializable)
可序列化涉及到范围锁(主要是用于解决聚集查询),所以没有幻读问题。
2.可重复读(READ-REPEATABLE)
在可重复读取(REPEATABLE READS)级别下,数据库系统会在整个事务期间保持读取锁和写入锁。但相较于可序列化,范围锁不会持有,所以幻读可能会出现。
Ps:MySQL的MVCC兼容此隔离级别,并且避免了幻读问题。
3.不可重复读(READ-COMMITTED)
在READ COMMITTED级别下,整个事务期间保持写入锁,但读取锁会在SELECT执行之后立即释放。所以也称为NON-REPEATABLE READ。
4.读未提交(READ-UNCOMMITED)
在此隔离级别下,脏读可能会出现。
Ps:此处需要注意脏读含义的理解。
事务A如果可以看见其它事务的中间状态,那么就是脏读。换句话说,一个事务能看见的数据,要么在其它事务开始之前,要么是之后。事务是将数据库状态从某个一致的状态,变成另一种一致的状态。
小结
以MySql为例,大多数应用采用的隔离级别为REPEATABLE READ
,所以SELECT读到的数据可能不是目前最新的(可见性?)。其实对大多数业务来说都OK,因为大多数业务并不要求全局的happen-before关系,而只要求某几个操作之间有。
MVCC
以隔离级别「READ-REPEATABLE」为例,按照上面「读写锁」的实现,那么读写依然是冲突的,能不能达到读写也不冲突呢?毕竟人类对性能的追求是无止境的。答案是肯定的。其思想上和Java的CopyOnWriteArrayList
极为类似。简单来说
就是通过”volatile+写锁”实现。这样就可以实现
读写不互斥(通过volatile);
还是以经典的Bob给Simith转账100刀为例.
事务A
start
update account set balance=balance where id=’bob’ and balance >=100
commit;
事务B
start;
select balance where id=’bob’
commit;
现在假设事务A和事务B并行执行,照例说事务B,应该会等事务A提交时候( 提交会释放锁),之后才会读到bob账户余额,事实上由于读余额时,并不会尝试获取写锁,所以无需等待。所以读写不互斥。
Ps:肯定有同学会疑惑,这样不就是读得不是最新的了嘛?的确,这样读到的数据不是最新的,但是为什么需要读到最新的呢,对大多读事务场景,并不需要读到最新值啊,如果是写事务的话,那么对业务属性是否满足更新条件,必须放在update的where子句中,而不能先select后update,当然,对大多数应用,先selelct 如果(不)满足,然后(不)更新,如果大多数条件都不用更新,那么这么写,可能会有性能优化,毕竟select 无锁,update可是必须要锁的。
显示锁
隐式锁
- select 隐式获取与释放读锁
写时复制(COW(Copy-On-Write))的