<>一、概述
我们日常在使用mysql的时候,可能同时会有多个事务对同一条或者同一批数据进行增删改查,然后会导致我们常说的脏写、脏读、不可重复读、幻读
等现象。其实本质上,出现出现这些问题的本质就是数据库的多事务并发问题,针对这些问题,数据库设计了事务隔离机制、锁机制、MVCC多版本并发控制机制
等来解决并发问题。接下来,我会详细的介绍这些机制,让大家深入理解数据库内部执行原理。
<>二、事务和ACID
事务是有一组sql语句组成的一个逻辑单元,具有如下四个属性:
1、原子性:事务是一个原子单元操作,多条sql多数据的修改要么全部成功,要么全部失败。
2、一致性:事务开始和结束都要保证数据一致性,也就是数据的修改都需要基于事务来进行。
3、隔离性:数据库提供一定的隔离机制,让多个事务并发执行的时候互不影响。
4、持久性:事务提交后,对数据的修改是永久性的。
1、并发事务带来的问题
脏写:两个或者多个事务选择对同一行数据进行写入,但是只有最后写入的成功的覆盖了其他事务的写入。
脏读:两个或多个事务并发读写,其中一个事务读到了别的事务还未提交的结果。
不可重复读:一个事务在不同时间内执行同样的sql,得到的结果不一致。
幻读:一个事务在不同时间内执行同样的sql,读到了别的事务新增的数据。
2、事务隔离级别
数据库提供了四大隔离级别:读未提交、读已提交、可重复读、串行化。下面是这四种隔离级别对并发问题的解决情况。
数据库的事务隔离级别越严格,所带来的并发问题副作用就越小,但是代价也是越高。因为事务隔离,在一定程度上就是让事务在”串行化“执行,这显然与”并发“是矛盾的。
不同的系统对读一致性和事务的隔离级别的要求是不同的,大多数应用对”不可重复读“和”幻读“并不敏感,他们更关心的是事务的并发能力。
查看当前数据库的事务隔离级别:
show variables like 'tx_isolation';
设置事务隔离级别:
set tx_isolation='REPEATABLE-READ';
Mysql默认的隔离级别是可重复读,Spring项目如果不指定隔离级别就默认使用该隔离级别。
<>三、锁机制
锁是计算机中协调多进程或线程并发访问某一资源的机制。在数据库中,除了对计算资源(cpu,ram,I/O)等的争抢,数据也是一种需要共享的资源。如何保证数据的有效性、准确性也是数据库必须解决的。下面来详细介绍一下锁机制。
1、锁的分类
从性能上来看,可以分为乐观锁(版本号实现)和悲观锁(串行化处理)。
从对数据的操作来看,可以分为读锁(也叫共享锁,Share)和写锁(也叫排它锁,eXclusive),都属于悲观锁。
从数据库操作粒度分,可以分为行锁和表锁。
2、行锁和表锁
表锁
加锁快,开销小,粒度大,并发度低,一般用于数据表迁移等场景。下面演示一下表锁的使用:
手动添加表的读锁和写锁:
lock table test1 read,test2 write;
查看表上加过的锁:
show open tables;
删除锁
unlock tables;
MyISAM引擎,对表加读锁。不会阻塞其他进程对表数据的读取操作,但是会阻塞对同一表的写请求。只有当读锁释放了后,其他进程才能进行写操作。
MyISAM引擎,对表加写锁。会阻塞其他进程对同一表的读写操作,只有当锁释放了后,才能进行其他操作。
行锁
加锁慢,开销大,粒度小,容易出现死锁,发生锁冲突的概率的,并发度大。InnoDB和 MyISAM的最大区别就是:InnoDB支持事务和InnoDB支持行锁.
一个session开始事务更新不提交,其他session的事务更新同一行记录会阻塞,更新其他数据则不会阻塞。
总结
MyISAM在执行select语句的时候,会自动给涉及的所有表加读锁。在执行update、delete、insert的时候会给涉及的所有表加写锁。
InnoDB在执行select语句的时候,除了串行化隔离级别外,都不会加锁。在执行update、delete、insert的时候会给涉及的行加行锁。
<>四、间隙锁和临键锁
mysql默认的隔离级别是可重复读,这个隔离级别会有幻读问题,那么有办法解决幻读问题吗?间隙锁,锁的是两个值之间的空隙,在一定程度上可以解决幻读,该锁只在
可重复读隔离级别下才生效。
1、间隙锁
在下面这张表中,就有间隙id为(41,47),(49,51),(52,100),(100,+∞)这四个区间。如果我们在本session中执行如下sql语句:
update recommend set recommendId = 10 where id > 50 and id < 70;
那么,其他session将无法在这个范围内所包含的行,和行记录所在的间隙区间内对数据进行修改。即无法在(49,100]区间内进行修改,
注意:最后的100也是包含在内的,左开右闭区间。
2、临键锁
临键锁,是间隙锁和行锁的结合,上述(49,100]区间也可以称为临键锁。
五、总结
1、InnoDB的行锁是加在索引上的,且查询的时候索引不能失效,否则行锁会失效变为表锁,其他session对该表的操作都会阻塞。
2、InnoDB实现的行锁,虽然说在加锁的性能上相比较MyISAM的表锁会有一些比较大的损耗,但是当并发量越来越大的时候,InnoDB的性能是远远高于MyISAM的表锁的。但是InnoDB的行锁也有脆弱的一面,如果我们使用不当,可能会比MyISAM的性能更差。
3、尽可能让所有数据的检索都通过索引完成,避免无索引行锁升级为表锁,降低并发。
4、合理的设计索引,尽量减少锁的范围。
5、尽可能减少索引条件范围,进而避免或减少间隙锁影响的范围。
6、尽量控制事务大小,减少锁定资源的时间,设计事务加锁的sql尽量放到事务的最后执行。
7、尽可能低的降低事务隔离级别,mysql默认使用的是可重复读隔离级别。