事务与锁

基础理论

ACID


  • 原子性(Atomic):事务内所有操作要么全部执行,要么全不执行
  • 一致性(Consistency):事务前后满足一致性,即符合一致约束条件,比如两个账户转账,两账户总金额一致
  • 隔离性(Isolation): 一个事务不影响其他,没提交前不会被看到
  • 持久性(Durability): 已经提交的不会丢失

事务结束方式


事务已两种形式终结

  • 提交(Commit)
  • 回滚(Rollback)


协调事务并发访问数据,加到锁后事务结束后才会放

按目标

  • 行锁:粒度小,并发强,速度慢,复杂易死锁
  • 页锁:介于行锁表锁之间
  • 表锁:粒度大,并发弱,速度快,简单

按行为

  • 共享锁(Share Lock,S锁,读锁):加共享锁后,其他事务依然可以读取
  • 独占锁(Exclusive Lock,X锁,写锁):加独占锁后,其他事务不能读写,资源上没有读写锁时才能加上
  • 更新锁(Update Lock,U锁,更新锁):资源上只能加一个,和读锁兼容,其他事务依然可读,预定修改,无需等待读锁释放就可以转成写锁

一般数据库都有读写锁,部分数据库有更新锁

死锁场景

  • 请求同一资源,都是先读后写
    两个事务都成功加到读锁,谁都不能继续升级为写锁
  • 请求不同资源,加锁顺序不同
    两个事务先分别获取锁,再请求对方

隔离级别


SQL92标准
SERIALIZABLE_READ > REPEATABLE_READ > READ_COMMITTED > READ_UNCOMMITTED
SERIALIZABLE时,读也加锁,读锁会阻塞写锁,容易造成死锁,比如两个事务都是先读后写,都加读锁,写锁加不到
SERIALIZABLE严格遵从互不干涉原则,一个事务使用数据时其他事务不能使用,效果是数据相关的所有事务排队执行
隔离级别通过控制如何加锁实现,不同隔离是放宽条件来提升性能,隔离程度越高,代价越高,数据库的并发性越差

读取问题


  • 脏读(Dirty Read)
    一个事务读取了另一事务没提交的数据,这部分数据有可能回滚
    违反隔离性,事务隔离级别应为READ_COMMITTED

  • 不可重复读(Unrepeatable Read)
    事务内两个相同查询返回不同数据,因为这部分数据被其他事务提交导致修改
    违反隔离性,事务隔离级别应为REPEATABLE_READ
    加行锁,事务拿到锁在提交前,别的事务不能对行操作

  • 幻读(Phantom Read)
    事务内两个相同查询返回不同数据,因为这部分数据被其他事务提交导致新增或者删除
    违反隔离性,事务隔离级别应为SERIALIZABLE_READ,事务串行执行
    加表锁,事务拿到锁在提交前,别的事务不能对表操作

更新问题


  • 第一类丢失:事务回滚,把其他事务提交的数据抹掉
    SQL92所有级别都不允许这种
  • 第二类丢失:事务提交,把其他事务提交的数据覆盖
    比如操作同一行,初始读到金额都是1000,两个事务同时在原基础上增加100,最终先后提交后金额变为1100
    一般采用局部手动悲观加锁或者乐观锁的方式维护版本号

默认级别


Oracle支持READ_COMMITED, SERIALIZABLE,默认隔离级别READ_COMMITTED
Mysql支持四种,默认级别REPEATABLE_READ