一 数据库事务概念
1.数据库事务
数据库操作的最小单元,是一组不可分割的逻辑操作单元。要么一起成功持久化,要么失败回滚。
2.事务的ACID特性
原子性(Atomic)
最小单元,不可分割了。
一致性(Consistency)
事务操作的数据及状态是一致的,写入的结果必须要符合预期规则,与会因为系统意外等原因导致状态不一致。
隔离性(Isolation)
不同事务操作数据之间不可见。
持久性(Durability)
事务一旦提交,数据更改就会永久保存。
3 事务并发带来的问题
脏读
事务A读到了事务B未提交的事务。
不可重复读
事务A读到了事务B未提交的事务,这时事务B回滚,事务A再次读到了另一结果。
幻读
事务A读取到了1条数据,这时事务B提交一条记录,事务A再次执行相同sql,读到了2条记录,结果是1条还是2条?
事务的隔离级别:
RU:read UNcommitted,未解决问题
RC: read committed,解决脏读
RR: repeatable read,解决不可重复读问题(此隔离级别未解决幻读问题,单MySQL使用的InnoDB通过锁和MVCC技术解决了幻读问题)
Serializable,解决所有问题。
效率RU > RC > RR > Serializable
MySQL默认事务隔离级别:RR
Oracle默认事务隔离级别: RC
二 MySQL的锁
1.MySQL锁的作用:用于控制不同事务对共享资源的并发访问。数据库锁大体上可以分为表锁和行锁。MyISA存储引擎只支持表锁,InnoDB支持行锁和表锁(通过锁住所有行间接实现)。
2.MySQL InnoDB的锁类型
2.1 共享锁-S锁 (行锁):Shared Locks
共享锁和共享锁可以共存,用于读与读之间,显式加锁方式为:
select * from table where xxx LOCK IN SHARE MODE;加锁
commit/rollback;释放锁
2.2 排它锁 -X锁(行锁):Exclusive Locks
不能与别的锁共存,包括别的排它锁,用于C U D(CRUD)操作加锁。
显式加锁方式:
在sql最后面加 for update;
commit/rollback; 释放锁;
InnoDB的行锁是通过给索引上的索引项加锁来实现的。只有通过索引条件进行数据检索时,InnoDB才会使用行级锁,否则InnoDB将使用表锁(锁住索引的所有记录)。因此执行cud语句也最好命中索引,否将会加表锁,锁住整张表。
2.3 意向共享锁(表锁):Intention Shared Locks
表示事务准备给数据行加入共享锁前,必须要先取得该表的IS锁,意向共享锁之间可以相互兼容。这里可以理解为Lock锁里面的tryLock方法一样的作用。
2.4 意向排它锁(表锁):Intention Exclusive Locks
表示事务准备给数据行加入排它锁前,必须要先取得该表的IX锁,意向排它锁之间可以相互兼容。
以上两种锁是表锁,相当于表是否被锁住的一个flag。
2.5 自增锁:Auto-Inc Locks
自增长的表级别锁,默认取值1,需要注意的是rollback后id的值就会丢失,会出现id取值不连续的情况。
行锁的算法:
临键锁:Next-key Locks
锁住记录+下一个区间(左开右闭)
当sql执行按照索引进行数据检索时,查询条件为范围查找(between and,<, > 等)并且有数据命中则此时sql语句加上的锁为Next-key locks,锁住索引记录 + 区间(左开由闭)。
表t中有如下数据。
InnoDB默认算法会将此表划分为区间:(-∞,1],(1,4],(4,7],(7,10],(10,+∞)。执行如下语句将会锁住命中的区间(4,7]和它的下一个区间(7,10]。此时再向(4,10]区间加加锁时将会阻塞。
select * from t where id>5 and id <9
InnoDB行锁采用临键锁算法作为默认算法的原因?
B+Tree组织数据存储是按照从小到大顺序的,InnoDB临键锁锁住了命中区间和下一个区间,可以防止下一个区间的插入操作,因此可以防止幻读。
间隙锁:Gap Locks
锁住数据不存在的区间(左开右开)
当sql执行按照索引进行数据的检索时,查询条件的数据不存在,这时临键锁就会退化为Gap locks,锁住索引不存在的区间。
如执行如下语句,将会产生一个Gap锁,锁住(1,4)区间。
select * from t where id=3
记录锁: Record Locks
锁住具体的索引项
当sql执行按照唯一性(primary,unique key)索引进行数据检索时,查询条件等值匹配且查询的数据存在时,临键锁退化为记录锁,锁住具体的索引项。如:
select * from t where id=4
三 利用锁解决脏读、不可重复读、幻读:
3.1 脏读产生原因:事务A对数据行进行cud操作时,事务B读取了数据行。
解决方法:对数据行加X锁,禁止别的事务读数据行。
3.2 不可重复读产生原因:事务A在两次读数据行期间,事务B对数据行进行了更新操作。
解决方法:读操作时加S锁,使得别的事务无法修改数据。
3.3 幻读产生原因:事务A在两次读数据行期间,事务B对数据行进行了新增操作。
解决方法:对操作数据行读操作时加临键锁,锁住下一个区间,使得写入操作无法进行。