Mysql事务和锁
ACID
- A:原子性,联级操作,要么一起执行,要么一起回滚,不存在执行了操作一,但是操作二失败了,,操作一、二是一个整体
- C:一致性,要么处于修改都成功,要么处于修改都失败,一致性的状态。
- I:隔离性,比如A 和 B同时开启事务,A操作了数据a,B读取a会读取之前的a,而不是刚被A操作的a
- D:持久性,提交过的事务,会持久性的保存在数据库当中,即使宕机还有效
|
并发引发
- 更新丢失:同时修改同一数据,A先修改完,B再修改,A的修改结果丢失。
脏读:更改但未提交的数据,被其他人查询到了未提交的数据,脏读
不可重复读:一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。但是可能会查到之前不存在的数据,幻读。
幻读(Phantom Reads):一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”
幻读案例:
例如在RR级别下,开启一个事务A,查询数据,开启一个数据B,插入新数据并且提交,name事务A虽然直接查询不到新数据,因为可重复读,但是可以update插入的新数据,并且update成功后,可以查到新数据。太神奇了。
脏读 和 不可重复读是两回事,禁止脏读,就是只读取提交后的数据,无法避免,一个事务第一次读数据还没结束事务,该数据被其他事务读取且删除而且提交了,再次读取就读取不到或者读取不一致,还是会发生不可重复读的情况。
事务级别(默认 REPEATABLE-READ,可能会幻读的)
- READ UNCOMMITTED ,可以读到其他事务修改甚至未提交的,–>引发脏读
READ COMMITTED ,其他事务对数据库的修改,只要已经提交,其修改的结果就是可见的,与这两个事务开始的先后顺序无关–>不可重复读,在一个事务中两次读取不一致
REPEATABLE READ,可重复读,完全适用MVCC,只能读取在它开始之前已经提交的事务对数据库的修改,在它开始以后,所有其他事务对数据库的修改对它来说均不可见
- Serializable(可串行化)
一条数据加锁了之后,其他事务无法更改这条数据。
set tx_isolation=’serializable’;
select @@tx_isolation;
事务A
事务B
因为隔离级别是 可串行化,事务A查询的时候把所有的查询行都加了共享锁,所以事务B阻塞,从而避免了幻读的可能性。
- RR不能解决幻读的解释
因为可重复读和提交读本身就是冲突的,默认可重复读是会出现幻读的,除非加共享锁取读取,就可以读取提交的最新的数据。
只有串行化才可以保证数据的绝对不冲突。
解释博客:
http://blog.sina.com.cn/s/blog_499740cb0100ugs7.html
锁机制
共享锁 和排他锁
https://www.cnblogs.com/boblogsbo/p/5602122.html
共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。
排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。
不带任何锁的普通查询,不管有没有锁,直接读老数据。
排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他的锁。mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select …for update语句,加共享锁可以使用select … lock in share mode语句。所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制开启事务未提交的DML操作都会自动加排他锁,这个时候只能进行不带锁的普通查询,而且只能查到老数据
意向锁
对表中某一行或者几行数据加锁的时候,都会给表加表级别的 IS IX锁,这样,事务B要给表加表锁的使用因为该表以及是IS 或者IX了,所以直接加锁失败,而不需要遍历表中的所有记录是否有锁。
mysql的行级锁
开始事务update一条数据的话,可以用共享锁 和 排他锁的方式读取非这条数据
- 锁命令lock tables t1 write|read;UNLOCK TABLES;SELECT * FROM `user_t` for update;
悲观锁、乐观锁
https://www.cnblogs.com/zhiqian-ali/p/6200874.html
悲观锁
特点是先获取锁,再进行业务操作,即“悲观”的认为获取锁是非常有可能失败的,因此要先确保获取锁成功再进行业务操作。通常所说的“一锁二查三更新”即指的是使用悲观锁。
select … for update操作来实现悲观锁,并发进行的时候,下一个事务如果有行交集的话就会阻塞。
也是串行化级别的采取的策略乐观锁
乐观锁的区别在于乐观的认为获取锁是很有可能成功的,如果真的不成功,被别人改过数据了,则回滚。
乐观锁在数据库上的实现完全是逻辑的,不需要数据库提供特殊的支持。一般的做法是在需要锁的数据上增加一个版本号,或者时间戳
乐观锁 + MYISAM 可以实现带事务控制的同时支持 高性能读取,又支持不频繁带事务控制的写。适用读多写少,且回滚开销不是特别大的场景。InnoDB就是乐观锁+版本控制管理,才实现的高并发的RR事务级别。1. SELECT data AS old_data, version AS old_version FROM …;2. 根据获取的数据进行业务操作,得到new_data和new_version3. UPDATE SET data = new_data, version = new_version WHERE version = old_versionif (updated row > 0) {// 乐观锁获取成功,操作完成} else {// 乐观锁获取失败,回滚并重试}区别
- 乐观锁是否在事务中其实都是无所谓的,悲观锁一定要有事务控制,
- 乐观锁在不发生取锁失败的情况下开销比悲观锁小,但是一旦发生失败回滚开销则比较大,特别是多个DML一起操作,所有执行成功的DML都要回滚,因此适合用在取锁失败概率比较小的场景,可以提升系统并发性能。因为锁表锁行是需要数据库开销的,即使未阻塞,也是有开销的。但是每次计算都是有效的,不存在计算之后数据回滚的风险。
- 悲观锁不支持高并发,容易死锁
死锁
事务A占用了 数据1 准备更新数据2时 发现事务B已经占用了数据2,所以事务A等待事务B解锁数据2.
事务B占用了数据2准备更新数据1时,发现事务A已经占用了数据1,所以事务B等待事务A解锁诗句1.
这个时候就是死锁了。
解决方案:
- 一次锁所有数据
- 保持锁的顺序
- 允许死锁,然后kill掉代价最小的事务。回滚之
MVCC
InnDB引擎的原理,不能避免幻读,特别是UPDATE操作,能解释为什么更新并发提交后,可以查看到新增加的数据,因为插入了一行新数据,且这个新数据的CTX=当前事务ID,所以更新之后就可以查的到了。
INSERT 新增的行往C里写入当前系统版本号。 这样新事物可以通过这个版本号保证不查到他
DELETE 为删除的行写D字段为当前系统版本号。
UPDATE 插入一行新数据写C为当前系统版本号,老数据写D为当前系统版本号。
SELECT 的时候只查 C<=当前版本 && (D > 当前版本 || D is not defined) 这样主要是为了在不加锁的情况下,一个事务能够读取到事务开始前已经存在且未被删除,且没有经过修改的数据。 也就是解决脏读的问题。即该事物开始之后的其他事务的各种修改事务提交都不会对当前事务的读取产生影响。