幻读的现象是在数据库事务中,在两次同样的查询条件下,得不一样的结果。发生这个现象的主要原因是,在两次查询之间,其他事务插入了新的数据,原来的事务没有对新增的数据进行过滤或者限制插入,导致第二次与第一次查询结果不一致。
所以要解决幻读有两种方式,第一种是在查询时过滤新增的数据;第二种是加锁,不让新数据插入成功。
第一种方式是基于MVCC实现,MVCC模式下每条数据都会带上事务ID、之前版本数据的引用(Undo Log),所以当前事务只要过滤掉比自己事务晚提交的记录,就能避免幻读。具体实现是在事务中第一次select时会构建ReadView,包含最小活跃事务ID、活跃事务ID列表、最大的事务ID、当前事务ID。通过这几个事务ID,就能判断出某条新增的记录是早于当前事务还是晚于。具体规则如下:
1. 小于最小活跃事务ID的都是已提交事务,当前事务可见
2. 大于最大的事务ID的,一定比当前事务晚,当前事务不可见
3. 在最小和最大事务ID之间的,已提交的事务的修改可见,未提交的事务的修改不可见
第二种方式是基于Next-key Lock(行锁+间隙锁)实现,如果在事务中使用了当前读,即读的时候使用了for update、lock on share mode等,会读到最新提交的数据,此时避免幻读就要通过对记录和记录间隙加锁,阻止其他事务修改当前记录或在记录之间插入新数据。