事情是这样的,今天遇到一个业务场景:按照比赛的时间start_at作为分页查询的条件获取赛程列表,首先初始化20条数据(数据库用的是MongoDB)
事情是这样的,今天遇到一个业务场景:按照比赛的时间start_at作为分页查询的条件获取赛程列表,首先初始化20条数据(数据库用的是MongoDB):
第1条记录: start_at: 2015-08-15 01:13:17.330299
第2条记录: start_at: 2015-08-15 01:13:18.330299
第3条记录: start_at: 2015-08-15 01:13:19.330299
第4条记录: start_at: 2015-08-15 01:13:20.330299
第5条记录: start_at: 2015-08-15 01:13:21.330299
第6条记录: start_at: 2015-08-15 01:13:22.330299
第7条记录: start_at: 2015-08-15 01:13:23.330299
第8条记录: start_at: 2015-08-15 01:13:24.330299
第9条记录: start_at: 2015-08-15 01:13:25.330299
第10条记录: start_at: 2015-08-15 01:13:26.330299
客户端请求的时候通过last_id
来确定下次从什么位置获取,第一次请求的时候不需要此参数,系统默认从第1条开始查询,此处假设page_size
为3,每次获取3条。第1次请求完,服务端会返回一个last_id
给客户端,那么这个last_id
是怎么生成的呢?服务端会把第3条记录的start_at从datetime转换成timestamps,返回一个int类型的时间戳给客户端,第次一的查询条件是:
cursor = conn.matches.find({}).sort([('start_at', 1)]).limit(3)
返回前3条数据以及last_id
(last_id
是根据第三条数据数据的的start_at
转换成时间戳之后的值)
last_id = time.mktime(start_at.timetuple())
>>> 1439572399.0 # 第三条记录 2015-08-15 01:13:19.330000 转换成时间戳的值
客户端发起第2次请求获取第2页的时候,把该数值传递到服务端,服务端接收到last_id=1439572399
后,做一次转换,转换成datetime类型:
start_at = datetime.datetime.fromtimestamp(last_id)
第二次查询的条件是:
cursor = conn.matches.find({'start_at': {'$gt': start_at}}).sort([('start_at', 1)]).limit(3)
于是碰到一个奇怪的问题:第二次查询返回的第一条数据和第一次查询返回的数据是一样的,感觉查询条件$gt
变成了gte
,这太不科学了,开始根本找不到原因,于是把初始化数据修改了一下:
第1条记录: start_at: 2015-08-14 11:05:21
第2条记录: start_at: 2015-08-14 11:05:22
第3条记录: start_at: 2015-08-14 11:05:23
第4条记录: start_at: 2015-08-14 11:05:24
第5条记录: start_at: 2015-08-14 11:05:25
第6条记录: start_at: 2015-08-14 11:05:26
第7条记录: start_at: 2015-08-14 11:05:27
第8条记录: start_at: 2015-08-14 11:05:28
第9条记录: start_at: 2015-08-14 11:05:29
第10条记录: start_at: 2015-08-14 11:05:30
然后重复上面的查询,此时返回的结果是正常的。究其原因是什么呢?从两份数据的对比,唯一的区别就是前者的时间带有_毫秒数_,为啥带上毫秒就出问题了呢,于是开始一步步调试发现一处细节,timestamp转换成datetime的时候,最后的毫秒丢失了:
start_at = datetime.datetime.fromtimestamp(1439572399)
>>> 2015-08-15 01:13:19
于是问题就可以解释的通了,没有带毫秒的2015-08-15 01:13:19
肯定是小于2015-08-15 01:13:19.330299
的,因此第二次查询的时候把第一次的查询返回的最后一条数据也查出来了。问题定位到了,那么就好解决了,原来最终的bug就出现在datetime转成timestamp的埋下的。
last_id = time.mktime(start_at.timetuple())
转换的时候,会自动忽略掉毫秒级别的值,解决的办法就是把毫秒数加上:
time.mktime(start_at.timetuple()) + start_at.microsecond * 0.000001
整个世界都清静了。
关注公众号「Python之禅」,回复「1024」免费获取Python资源