事务

ACID属性(原子性,一致性,隔离性,持久性)

  • 并发事务可能带来的问题
    • 更新丢失(脏写):事务之间相互覆盖结果
    • 脏读:读到其他事务未提交的数据 dirty read
    • 不可重复读:一次事务中,读到其他事务已提交的变更结果 不可重复读
    • 幻读:一次事务中,读到其他事务新增的数据 幻读
  • 事务隔离级别
    • 读未提交(READ-UNCOMMITTED)
    • 读已提交(READ-COMMITTED)
    • 可重复读(REPEATABLE-READ) 没有解决幻读问题,在对新数据执行一次更新操作时,会感知到新增的数据。 可通过间隙锁或修改隔离级别解决幻读问题。
    • 可串行化(SERIALIZABLE)

查看/设置事务隔离级别:

mysql> show global variables like 'tx_isolation';
# 读未提交
mysql> set tx_isolation='read-uncommitted';
# 读已提交
mysql> set tx_isolation='read-committed';
# 可重复读
mysql> set tx_isolation='repeatable-read';
# 串行化
mysql> set tx_isolation='serializable';

  • 乐观锁和悲观锁
    • 乐观锁:通过版本号进行判断数据是否有被修改过,没有锁等待,类似CAS。
    • 悲观锁:事务之间会相互等待
  • 读锁(共享锁,S锁)和写锁(排他锁,X锁)
    • 读锁
    • 写锁
  • 意向锁(IS锁和IX锁)

意向锁是表级锁且意向锁之间不互斥。当事务加表锁时,需要检查当前表是否存在行锁,意向锁就是为了提高检查效率的,因为加行锁之前,会先加对应的意向锁,如加S锁前,先加IS锁;加X锁前,先加IX锁。所以INNODB支持表锁和行锁是可以共存的。

  • 表锁和行锁
    • 表锁:一般会在数据迁移的时候使用,保证数据不会变。
    • 行锁:锁住单行记录,可能会产生死锁(大部分情况mysql会自动检测到死锁,并回滚产生死锁的事务)。InnoDB的行锁是针对索引的。
    select * from users where inx_column=xxx for update;
    
  • 间隙锁
select * from users where column=xxx lock in share mode;

锁住记录之间以及值所在的区间所有的行,在可重复读隔离级别下生效。尽量避免间隙锁。 以下示例中,mysql会锁住(5,10]以及(10, 20]区间的所有行。其中(3,20]这个区间也叫临键锁

mysql> select * from users;
+----+---------+
| id | name    |
+----+---------+
|  4 | heoller |
|  5 | wjy     |
| 10 | wjy1    |
| 20 | wjy2    |
+----+---------+
4 rows in set (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update users set name='aaa' where id > 8 and id < 15;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

MyISAM存储引擎不支持事务;执行查询时,会在表上加读锁;非读操作时,会在表上加写锁,且不支持行锁

select for update到底加了哪些锁?

RC隔离级别

  • 命中主键索引记录:X锁,IX锁
  • 命中唯一索引记录:X锁(唯一索引),X锁(主键索引),IX锁
  • 命中普通索引记录:X锁(普通索引),X锁(主键索引),IX锁
  • 未命中普通索引记录:IX锁
  • 未走索引:X锁(全表扫描命中的主键索引记录),IX锁

RR隔离级别

  • 命中主键索引记录:X锁,IX锁
  • 命中唯一索引记录:X锁(唯一索引),X锁(主键索引),IX锁
  • 命中普通索引记录:X锁(普通索引),X锁(主键索引),IX锁,Gap锁(索引值前后区间)
  • 未命中普通索引记录:IX锁,Gap锁(索引值前后区间)
  • 未走索引:虚拟全表行锁(锁表)