按日期范围查询时,先明确三个契约:字段存的是日期还是字符串,边界属于哪个时区,结果按哪个字段排序。看到ISODate显示样式,不等于这些问题已经解决。
左闭右开更容易衔接相邻区间
$gte 包含起点,$lt 不包含终点。查询某个业务自然日时,可以用“当天零点至次日零点”,避免靠23:59:59之类的人工末尾时刻遗漏毫秒。
以下是Java 8时间API与MongoDB Java Driver构建器的示例,查询上海时区2020年6月11日:
ZoneId zone = ZoneId.of("Asia/Shanghai");
LocalDate day = LocalDate.of(2020, 6, 11);
Date startTime = Date.from(day.atStartOfDay(zone).toInstant());
Date endTime = Date.from(day.plusDays(1).atStartOfDay(zone).toInstant());
Bson filter = Filters.and(
Filters.gte("announceCreateTime", startTime),
Filters.lt("announceCreateTime", endTime)
);
Bson order = Sorts.orderBy(
Sorts.descending("announceCreateTime"),
Sorts.descending("_id")
);
FindIterable<Document> result = collection.find(filter).sort(order);
需要对应的java.time、java.util.Date、org.bson及驱动model类导入,collection来自实际项目。采用业务时区的次日开始,不能对所有时区一律加固定24小时替代;夏令时切换可能改变一天的长度。
过滤字段与排序字段要分别确定
原记录用 announceCreateTime 筛选,却按 createTime 排序:
new BasicDBObject("createTime", -1)
它们可能有不同业务含义。上面的教学示例选择按同一时间字段排序,是明确的示例选择,不是认定历史项目一定写错。应先查字段定义,再决定按发布时间、录入时间还是其他时间排列。
1为升序,-1为降序;时间相同可用唯一键补排序,但并发写入下跨页结果仍可能变化。MongoDB排序说明
BSON日期与字符串不能只看显示格式比较
BSON Date记录自Unix epoch起的毫秒数;ISODate是Shell构造/展示日期的一种写法,不是另一个必须手工拼出的字符串类型。BSON Date说明
Java Date经相应驱动codec编码为日期值;若实际字段历史上存成字符串,应先确认类型分布与迁移方案,不要混用字符串和Date边界期待相同比较。
相邻区间的起点、终点、毫秒值和不同时区输入,都应有边界样本。过滤与排序是否使用索引,则结合真实数据分布和explain判断,不能仅凭加了sort就保证高效。