任何数除以0结果都是无穷大,不同的数据库客户端库对这个结果无穷大的处理都不一样,有一些问题值得我们去注意。
比如这样的一个sql:
select os_id,browser_id,browser_id/os_id gg from example
之前的脚本在mysql上执行通过go客户端读取结果是不会存在问题的,但是放到clickhouse上面执行,读取结果只会就发现会存在问题。具体问题请看下面我详细讲述。
问题:
自从通过用clickhouse做分析数据的仓库后,我们需要通过sql读取clickhouse的结果存储到mysql存储里面,让用户能读取,当时脚本一切都好好地,突然发现某一天的数据丢失了,重跑也没有数据,经过查证发现原始数据是有的,不可能这一天都没有数据。
问题追踪:
当时查了日志也没有发现日志错误,也没有发现有奔溃什么的,那天的统计原始数据也不多,后面就把统计之后的结果数据打印出来,放到线上去跑,后面发现返回的结果和之前的几天能读出来的数据多了个+Inf,同时发现之前的写入到mysql的脚本程序把错误也给忽略了(这里是代码质量问题,目前先不谈),查到这里,我就开始写个demo出来验证一下。
客户端代码如下:
package main
import (
"encoding/json"
"fmt"
_ "github.com/ClickHouse/clickhouse-go"
"github.com/jmoiron/sqlx"
)
func main() {
connect, err := sqlx.Open("clickhouse", "tcp://127.0.0.1:9000?database=default&debug=true")
if err != nil {
fmt.Println(err)
return
}
sql := "select os_id,browser_id,browser_id/os_id gg from example"
rows, err := connect.Queryx(sql)
if err != nil {
fmt.Printf("connect DB Queryx, err:%v\n", err)
return
}
var list []interface{}
for rows.Next() {
result := map[string]interface{}{}
err := rows.MapScan(result)
if err != nil {
fmt.Printf("scan err:%v", err)
return
}
fmt.Println(result)
t, err := json.Marshal(result)
fmt.Printf("xxx:%s%v\n", string(t), err)
list = append(list, result)
}
t, err := json.Marshal(list)
fmt.Printf("list:%s%v\n", string(t), err)
}
执行结果如下:
map[browser_id:54 gg:2.347826086956522 os_id:23]
xxx:{"browser_id":54,"gg":2.347826086956522,"os_id":23}<nil>
map[browser_id:54 gg:2.347826086956522 os_id:23]
xxx:{"browser_id":54,"gg":2.347826086956522,"os_id":23}<nil>
map[browser_id:54 gg:+Inf os_id:0]
xxx:json: unsupported value: +Inf
其实只有这里的json.Marshal才会报错:
json: unsupported value: +Inf
问题追踪完之后,通过测试确定问题是Inf造成了报错,然后数据结果没有写入到mysql中。
问题深入:
上面我们其实已经找到了问题,然而我并不满足此,通过深入发现同样的sql,mysql却不会存在报错,go的mysql客户端把无穷大转成了sql.RawBytes,go的clickhouse却直接把无穷大转化成一种数据类型+Inf,这种数据类型只有go的clickhouse客户端才有,json的库并不能处理这个类型。
问题解决:
1:把代码中该加error的地方加上
2:如果读clickhouse的数据的sql存在相处的类型,我们都必须手动处理结果为无穷大的情况:如果除数等于0则把结果赋值成0,这样计算的结果就能正常写到mysql中。