并发操作会带来一系列的问题
- 更新丢失(lost update)
当两个或多个事务选择了同一行然后基于最初选定的值更新改行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新的问题,最后更新覆盖了由其他事务所做的更新
- 脏读 (Dirty reads)
一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致的状态;这时,另一个事务也读取同一条基础,如果不加控制,第二个事务读取这些"脏"数据,并据此作进一步的处理,就会产生未提交的数据的依赖关系,这种叫做"脏读"
- 不可重复读(Non-Repeatable reads)
一个事务在读取某些数据的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生改变,或者某些记录被删除了!! 这就叫不可重复读
(不符合隔离性)
- 幻读
一个事务按相同的查询条件读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象叫做"幻读",好像重来没出现过
设置了 mysql 事务隔离级别, mysql对于操作同一行数据会自动加锁
对于可重复读, mysql使用的是 MVCC 机制 ,multi version concurrent control,多版本并发控制机制
select 操作不会更新版本,每次查询都会做一次快照,用的是快照版本。然后修改后,真实数据以及和快照版本不一致了,但是 mysql为了让逻辑准确,更新的时候会那数据库最新的数据进行更新,而读是读的快照版本的数据。
商品超卖,就是这种原因。
https://blog.csdn.net/huaishu/article/details/89924250
这种就是当前读和快照读的区别
快照读会让性能高一点。所以 select 出来再 update 会有 bug
注意点:sql应该这样写: update account set balance = balance-50 where id = 1;
可重复读的话,select不会更新版本好,是快照读(历史版本),而insert 、update和delete 会更新版本好,是当前读(当前版本)
所以更新之后再开查就可能会有幻读的现象:
session1: select * from account;
session2: insert into account (name,money) values('ss',100);
session1: update account set name = 'lyr' where id = 1 ;
session1: select * from account;
如果 session1: 没有update account 这张表,会用快照读,不会出现幻读的问题
然而: session1: update 了 account这张表,那么就会从快照读改当前读
而session2 这个时候 插入了一条 name='ss', money=100 的数据
session1 再来查表,发现无缘无故有多了一条,这个就像幻觉一样的数据(幽灵般的出现了)
这个就是幻读
改成 串行化,那就什么问题也没有了,但是不应该这样,因为效率低
mysql 使用可重复读 兼顾了效率尽量的解决了脏读和不可重复读的问题
查看mysql近期死锁的日志信息:
show engine innodb statusG
mysql的优化:
- 尽可能让所有数据检索通过索引完成,避免无索引行锁升级为表锁
- 合理设计索引,尽量缩小锁的范围
- 尽可能检索检索条件,避免间隙锁
- 尽量控制事务大小,减少锁定资源量和时间长度
- 尽可能低级别的事务隔离
这样就可以解决并发问题了
- update account set money = money-1 where id='llyr' and money >0;
mysql可以使用一个间隙锁,只要是范围的话 mysql自动加锁,一般不会有幻读问题
条件 是一个范围,mysql就加锁了
这样可以解决幻读问题
https://blog.csdn.net/spring_model/article/details/53992450
还有其他的锁,比如 for update 独占锁之类的
mysql 开启事务的时候 update语句的时候加锁,COMMIT后释放
所以sql 写的好,一般不会有问题。