Mysql的事务的隔离级别详解
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。
事务隔离级别共有4种:
事务隔离级别 | 脏读 | 不可重复度 | 幻读 |
1.未提交读 | 是 | 是 | 是 |
2.提交读(不可重复读) | 否 | 是 | 是 |
3.可重复读 | 否 | 否 | 是 |
4.串行化 | 否 | 否 | 否 |
mysql的默认事务隔离级别为可重复读,而Oracle、Sqlserver的默认事务隔离级别都为提交读(不可重复读)
1.InnoDB 支持表锁和行锁,使用索引作为检索条件修改数据时采用行锁,否则采用表锁。锁表时在其它的事务中执行会发生等待的现象,时间长的话会有报错提示。
2 InnoDB 自动给修改操作加锁,给查询操作不自动加锁
3.行锁相对于表锁来说,优势在于高并发场景下表现更突出,毕竟锁的粒度小。
4. 当表的大部分数据需要被修改,或者是多表复杂关联查询时,建议使用表锁优于行锁。
5.为了保证数据的一致完整性,任何一个数据库都存在锁定机制。锁定机制的优劣直接影响到一个数据库的并发处理能力和性能。
举例说明:不可重复读
1.事务隔离级别为不可重复读
2.在两个事务同时开启时
3.事务B中,在事务A操作增删改之前查询一次数据
4.事务A做了更新或删除的操作,并提交
5.事务B中,在事务A操作增删改之后查询一次数据。两次查询结果不一致,可以看见事务A中已经提交的操作
脏读:事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也称为脏读。
幻读:指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新记录,当之前的事务再次读取该范围内的记录时,会产生幻行。InnoDB和XtraDB存储引擎通过多版本并发控制(MVVC)解决了幻读的问题。
事务隔离级别为可重复读的情况下:
序列化和可重复读的实验测试结果:基于MySQL版本5.7.22
可重复读 | 事务一 | 事务二 | 事务一结果 | 事务二结果 |
步骤一 | begin | begin | ||
步骤二 | select | select | 结果(1) | 结果(2) |
步骤三 | update id=9 | |||
步骤四 | select | select | 出现update的结果(3) | 与(2)一致(4) |
步骤五 | update id=9 | 此时阻塞状态(可能超时) | ||
步骤六 | select | select | 与(3)结果一致(5) | 与(2)结果一致(6) |
步骤七 | commit | |||
步骤八 | select | select | 与(3)一致(7) | 与(6)一致(8)----------此时体现了可重复读的特点,无论事务一如何修改,是否提交,事务二查询的结果是一致的。 |
步骤九 | update id=9 | 这里有人说mysql没有彻底解决幻行的问题,我认为那是概念的混淆,串行化是可以避免这个问题的。 | 事务一提交后执行成功,此时结果按照事务二的结果为准,更新时所以用id为更新条件,否则可能更新失败,应为这个条数据的内容已经被事务一更改 | |
步骤十 | select | select | 与(3)一致(9) | 出现事务二update的结果(10) |
步骤十一 | commit | |||
步骤十二 | select | select | 与(12)一致(11) | 与(10)一致(12) |
可重复读 | 事务一 | 事务二 | 事务一结果 | 事务二结果 |
步骤一 | begin | begin | ||
步骤二 | select | select | 结果(1) | 结果(2) |
步骤三 | insert | |||
步骤四 | select | select | 出现i事务一nsert的结果(3) | 与(2)一致(4) |
步骤五 | insert | |||
步骤六 | select | select | 与(3)结果一致(5) | 出现事务二insert的结果(6)但是如果id是自增的情况,id会在事务一之后,因为事务先执行的操作,即使没有提交。 |
步骤七 | commit | |||
步骤八 | select | select | 与(3)一致(7) | 与(6)一致(8)----------按照幻读的概念此时应该出现幻行,但是没有出现事务一增加的数据,应为mysql已经解决的幻行的问题。 |
步骤九 | update /delete id=事务一新增的id | 虽然看不见数据,但是可以对数据进行更新和删除修改的操作,因为数据已经实际存在了,查询的时候是从视图中得到的数据。而更新不是,并且更新完就可以看见这条数据 | ||
步骤十 | select | select | 与(3)一致(9) | 出现事务二update/delete的结果(10) |
步骤十一 | commit | |||
步骤十二 | select | select | 与(12)一致(11) | 与(10)一致(12) |
共享锁和排他锁:也叫读锁和写锁。
读锁是共享的,或者说是相互不阻塞的。即多个客户在同一时刻可以同时读取同一个资源,互不干扰。
写锁是排他的,也就是说一个写锁会阻塞其他的写锁和读锁。只有这样,才能确保在给定的时间里,只有一个用户能执行写入,并防止其他用户读取正在写入的同一资源。
我的实验结果总结:
可重复读:update、insert加的是读锁,因为其他的事务里面是可以读取数据的,但是不能做修改的操作。
串行化:select 是读锁,因为在select的时候,其他事务是只能select。
update、insert加的是排他锁,因为在update、insert的时候,其他事务是不能select、insert、delete.
简单来说,SERIALIZABLE会在读取的每一行数据上都加锁。
手动实现加锁
共享锁:select * from tableName where ... + lock in share more
排他锁:select * from tableName where ... + for update
查询事务的隔离级别:select @@tx_isolation;
设置事务的隔离级别:SET session TRANSACTION ISOLATION LEVEL Serializable;(参数可以为:Read uncommitted,Read committed,repeatable read,Serializable)
开启事务:start transaction;或者begin;
提交事务:commit;