背景:实际django项目中遇到这样一个问题,两个进程A,B去读写MySQL数据库,A 在insert m 完成后通过redis 通知B去读MySQL。如下图所示,在进程B允许过程中,往数据库多插入几十条数据后,进程B依然查询不到。如下图所示:实际数据表中有6250,但是在进程中查询只有6218条,他对进程启动动新增的数据都查询不到。
问题:A insert完成后,这时B并不能查询到新增的记录m。但是通过其他客户端工具可以看到新增的m记录,特别的是B进程在重启后也能看到记录m。
当然更有意思的是这种现象在开发环境中并不会出现,只在测试环境中会有该问题。
分析1:由于项目使用的django技术,对MySQL查询和插入操作也是django封装进行,加上数据库确实存在该记录。所以怀疑是django没有去真实查询数据库。
排查过程1:到底有没有真实去查数据库?由于该问题是可以复现的,所有使用python manage.py shell 进来一步步去查。
第一,tcpdump 抓包可以搞定,第二,如果是测试开发环境,可以把MySQL 查询日志 的打开,通过日志去看是否进行查询动作。
这里使用方法1,在进程B的机器上执行 tcpdump port 3306, 如果想通过wireshrk 查看详细信息可以使用:tcpdump -w mysql.pcap port 3306 and host xxx ,打开
mysql.pcap就能看确实进行了查询,如图一:
分析2:看来B是去查询MySQL了。但是MySQL确实没有返回m,看来是django 使用MySQL有些配置上有问题(不要轻易怀疑是MySQL的问题,毕竟它很成熟,一般都是使用上的不当)。这次为了证明这个猜想,需要打开MySQL的日志了。
排查过程2:找到my.cnf 在[mysqld] 下增加 log=/var/log/mysql/mysql.log 的配置。重启msyql(注:该操作仅限于在开发测试环境排查问题用,在生产环境慎用影响性能)
然后我们用tail -f /var/log/mysql/mysql.log 来观察 A 和 B 到底在干嘛。如图二:
通过查询日志可以知道的是,数据确实保存到了数据库中,其他客户端也可以查出。但是进程B查不出,并且B在开始时设置了set autocommit=0表示要手动提交事务。由于数据库的事务隔离
级别的不同会导致两个事务查询出不同的结果。关于事务隔离级别的话在另一篇文章中有详细的介绍。
可以通过下边的语句来看数据库的隔离级别是什么。当然操作具体表时还需要看表的引擎,比如MyISAM 是不支持事务的,而InnoDB是支持事务的。也就是说不管你的事务隔离机制如何, MyISAM 类型的表都可以立即查询到已经真实存储在数据库中的记录。
select @@TX_ISOLATION;
解决方法:
找到了原因解决方法就方便多了。
(1)如果觉得没有必要用事务,也就是每条语句执行后就可以直接持久化到数据,那就把django 中的 ‘django.middleware.transaction.TransactionMiddleware’去掉
(2)光是查询的话可以使用下边的代码:
from django.db import transaction with transaction.autocommit(): #业务代码
(3)第三个就是修改数据库的事务隔离级别(一般不建议)
(4)修改表的数据引擎,刚开始背景中介绍的开发环境不存在,测试环境存在,原因就是表引擎不同。
总结:
遇到问题后,首先要冷静分析现象出现的场景。什么情况下会复现该问题。尽量能使用差异点来定位问题可能的原因。
就像上边这个现象,B在重启后可以查询到m。利用其它客户端也能查询到m,唯独在B运行过程中查询不到,就可以根据B运行中和B重启的差异来分析。
当然如果对数据库事务熟悉的同学应该会敏感的想到可能的原因。也就不用去看msyql log 了。