前提:关闭自动提交模拟多线程情形:set auto_commit=0
update:如果同时对于同一行进行update,那么后更改的线程将进入阻塞
事务1:更新成功未提交
事务2:等待事务1提交并阻塞
如果等待时间过长,则mysql会让阻塞事务自动放弃锁的争夺,需要重新发起事务
事务2阻塞等待过长报错,要求重新发起事务:
如果事务2等待没有超时,则等待过后更新成功,这时事务2没有提交,则是更新到事务2的快照中。那么如果事务1提交,并开启一个事务读取(我们在事务1中写查询语句,mysql会自动帮我们开启一个事务,但是不会自动提交,因为之前已经设置过自动提交了,前提是innerDB的engine),是读取不到事务2未提交的数据,只有事务2提交,并且事务1重新开启一个读取事务读取才能读取到事务2提交的数据。
如果我们要求即时性更新读取,那么读取不应该用事务所管理,所以在项目优化过程中,我们的读取事务的transactional注解对于读取业务都是设置为support,也就是自身读取则不设置事务,但是如果参与事务传播,则要受到事务的约束。
update:如果同时对于不同行进行update,那么两者都不会阻塞,但是只有两者都提交,并且读取事务都提交过后新开启读取事务,才能看到两个事务都更改的结果。不然也只是看到了快照。
insert:在主键没有自增的表中插入数据
主键不自增,则需要我们自行维护自增,但是有可能出现这样的情况,如果两个事务都将主键自增为10,并向表中插入数据,会发生什么情况
事务1:阻塞,等待事务2提交
事务2:更新成功未提交:
如果我们事务2一旦提交,则会发生这样的现象:
事务1:在事务2提交后报错
事务2:提交成功没事
原因是因为主键重复了,因为之前事务2提交后表中已经有了主键id为10的记录了,这时候如果事务1等待事务2提交后再向
表中插入数据则会因为主键不能重复而报错。
所以这就是为什么阿里关于mysql以及并发优化的书籍中对主键的建议是自增,因为如果不自增自行维护的话,很可能出现上述插入失败的案例,而且插入还会阻塞,严重影响体验和并发效率。
insert:在主键自增的表中插入数据
如果在维护了主键id为自增的表中插入数据,那又会如何呢?
事务1:执行插入成功没有阻塞
事务2:执行插入成功没有阻塞
我们可以看到,在维护了主键自增的表中插入数据,没有发生阻塞排队现象,并发效率高!
那么我们在两个事务没有提交前,查看两者快照的区别
事务1快照:插入成功,并且主键为7
事务2快照:插入成功,并且主键为8
我们可以看到,两者快照不同,并且主键id也不相同,因为事务发生肯定有先后,即使同时发生,也只是会有短暂的停顿而不会造成阻塞,并且由于主键自增,更是不会发生主键相同而出现的插入失败!
但是我们考虑:如果表维护了主键自增,数据很多并且建立有索引,那么插入效率必然会降低,那么即使有mvcc行级锁来提高并发,可能在线程竞争激烈情况下,插入效率依然很慢,所以在频繁增删的表上尽量不要建立索引或者不要建立过多索引。如果需要建立索引,那么尽量建立复合索引,并对sql优化,尽量不要使得索引失效。在增删与查询之间进行取舍和优化。
遗留问题:
1.主键是否可以作为version字段作为乐观锁,version字段是数据库自带的隐式字段还是需要我们自行维护?
2.insert在主键自增的表上进行并发插入,那么事务肯定是拿走快照进行插入的,那么他们是怎么保证更新自身快照过后还能保持主键不一致并根据插入时间先后排序的?mysql是通过什么手段来做到这一点的?换句话说,假设事务1先于事务2插入记录,那么他的主键就是当前最大主键加1,我们假设为11。那么紧接着按照事实来看,事务2的插入则是将主键置为12,那么事务2是如何得知主键11已经被占用了呢?