问题:在团队的测试框架上使用Python和公司的db客户端实现连接池功能,db连接用的是pymysql。在跑demo时,出现了诡异的现象:多次select查询表中数据,所有的查询结果都是准确无误的。但在涉及写-读操作时,问题出现。例如,先写库(调服务接口去下单),再去db读刚刚写入的订单数据。连接池中的连接只有在第1次去查询的时候能查到刚刚落库的订单数据,之后就查不到了(查询结果为空)。比如连接池大小设置为2。那么只有前2次查询能查到最新写入的数据,第3次开始就查不到最新的数据了。

解决:一开始对连接池和mysql没有很深入的理解,怀疑是连接池的实现有问题。但经过反复打断点,看连接池底层源码后,发现连接池的功能是没问题的。然后怀疑是不是从池中取连接时的线程锁没有生效,但是demo中的case都是顺序执行的,只有1个线程,可以排除线程不安全的问题,即问题不在线程锁上。再然后怀疑是公司的db主-从同步有延迟,因为写操作走的主库,读操作是走的从库。同事联系公司的dba同学,查了半天,没发现有问题。而且理论上,即使有延迟,也不可能是每次必现,否则公司的rd们早就炸翻天了。很诡异的问题...

自己回想为什么连续的只读操作就没问题,但凡涉及到写-读操作就有问题呢?把sql改一下试试,不用接口返回的订单id作为查询条件去查库,而是通过时间去查库中的最新订单,看与刚刚下过的订单数据是否一致。结果诡异的现象出现了,比如连接池大小设置为2,现在有5条下单用例,结果如下:

根据您的描述,您在使用pymysql查询数据时遇到了问题。每个连接,在第2次开始,查到的数据都和第1次使用时一样,好像有“缓存”一般。这个问题可能是由于没有将autocommit置为True导致的。初始化新连接时,要将autocommit置为True,默认为False。自己试了下,果然置为True后,就能查到最新写入的数据了。

关于SELECT语句为什么需要commit的问题,可以参考下面的链接中提到的REPEATABLE READ的概念。

事务是可以提交或回滚的原子操作单元。MySQL中只有Innodb(MySQL默认的引擎)数据库引擎才支持事务,事务具有ACID特征:

- atomicity(原子性):一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

- consistency(一致性):在每次提交或回滚之后,以及在事务进行过程中,数据库始终处于一致状态。如果跨多个表更新相关数据,则查询将看到所有表中的旧值或新值,而不是旧值和新值的混合。

- isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括:

- read uncommitted(读未提交内容)-->read committed(读已提交内容)-->repeatable read(可重复读)-->serializable(串行)。

- 隔离级别依次增强,但是导致的问题是并发能力的减弱。

- durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

脏读、幻读、不可重复读是数据库中常见的问题。其中:

- 脏读是指在一个事务读取了另一个事务未提交的数据;

- 幻读是指在一个事务中多次读取同一范围的记录时,得到了比其他事务更多的记录;

- 不可重复读是指在一个事务中多次读取同一行记录时,第一次读取结果为第一页数据而第二次却发现第一页已经被其他事务更改过了。

不可重复读和幻读的区别:

- 不可重复读是指事务A前后两次读取同一行数据的过程中,因为事务B对该行数据做了update的提交,导致事务A前后两次查询结果不一致。

- 幻读是指事务A前后两次条件相同的查询过程中,因为事务B的insert/delete提交,导致事务A第2次查询结果相对第1次结果出现了新行或旧行丢失。

即另一个事务的update操作会导致不可重复读,insert/delete会导致幻读。

一个事务的查询不会受到另一个事务update操作的影响,事务的所有查询都读取该事务启动时刻的数据快照,即两次读取的同一行数据是一致的。这可以避免不可重复读,但仍会出现幻读。No

No

Yes

Serializable(串行)

最严格的隔离级别。事务都是串行执行,读数据也会加锁,读会阻塞写,写也会阻塞读。可能导致大量的超时现象和锁竞争。No

No

No

快照(snapshot)

某一时刻的数据,即使后续其他事务更新了数据,该时刻的数据快照仍保持不变。被特定的隔离级别使用,以允许一致性读。一致性读(consistent read)也称为快照读。InnoDB确保并发时,事务A不会读取由事务B更新的信息,即使事务B已提交。原因是一致读使用基于某个时间点的数据快照。如果查询的数据已被另一个事务更改,则根据撤消日志(undo log)的内容重建原始数据。数据快照的时间选取:

- 隔离级别:快照时间:repeatable read

- 事务中第一次读操作的时刻read committed

- 事务中每次快照读时重置快照时间

一致性读是InnoDB在read committed和repeatable read隔离级别中select语句的默认模式。解析:ok,根据以上概念,查了下数据库的事务隔离级别,果然是repeatable read:mysql> select @@tx_isolation; +-----------------+ | @@tx_isolation | +-----------------+ | REPEATABLE-READ | +-----------------+现在回到最初的问题:基于InnoDB默认的repeatable read事务隔离和select为快照读可知,连接池中的连接在初始化时,没有设置autocommit为True,会导致复用同一个连接的多次select查询其实都在一个事务内,且都为快照读,这样,每个select查的数据源并不是表中的最新数据,而是第1个查询时的快照,因此导致查不到表中其他事务(下单接口的写入操作)写入的最新数据。将autocommit设置为True后,每个事务中只有1个查询,下一个查询属于一个全新的事务,这样就能读到新事务开始前的最新数据了。最后,为了避免出现幻读和不可重复读现象,可以将隔离级别设置为可重复读或串行化。

您好,MySQL的隔离级别有四种,分别是读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。其中,可重复读是MySQL默认的隔离级别。在可重复读隔离级别下,MySQL会创建一个一致性视图(Consistent View),该视图反映了事务开始时刻数据库的快照。MVCC机制会生成一个数据请求时间点的一致性数据快照 (Snapshot), 并用这个快照来提供一定级别 (语句级或事务级) 的一致性读取。从用户的角度来看,好象是数据库可以提供同一数据的多个版本(系统版本号和事务版本号) 。

关于幻读问题,repeatable read不能避免幻读,但是可以通过使用快照读来解决幻读问题 。