把地震目录灌进SQLite前,我先做了三件事
本地要做一个按区域、按震级筛历史地震的小工具,数据源用的是公开的 CSV 目录(日期时间、经纬度、深度、震级、地点),一个区域十年的记录大概 12 万行,文件 20 MB 出头。第一版直接 INSERT,跑了两分四十秒;更麻烦的是按经纬度框查询时,每次都要全表扫,一个框选要等三秒。这才有了下面三件事。
第一,先不急着插数据,把表结构和索引定下来。时间统一存成 TEXT 的 ISO 格式(2023-05-01T12:03:44),排序和范围比较都靠字符串,省掉时区换算的麻烦;经纬度用 REAL。
CREATE TABLE quake (
id INTEGER PRIMARY KEY,
origin_time TEXT NOT NULL,
lon REAL NOT NULL,
lat REAL NOT NULL,
depth_km REAL,
mag REAL,
place TEXT
);
CREATE INDEX idx_time ON quake(origin_time);
CREATE INDEX idx_mag ON quake(mag);
第二,批量插入放进一个事务里,并且用 executemany。单条自动提交是最大的坑,改成下面这样之后,同样的数据十几秒就完事。
conn.execute("BEGIN")
conn.executemany(
"INSERT INTO quake(origin_time,lon,lat,depth_km,mag,place) VALUES(?,?,?,?,?,?)",
rows,
)
conn.commit()
第三,经纬度框选不用 BETWEEN lon AND ... 硬扫,而是先按时间索引缩小范围,再在结果里过滤经纬度。十年数据里,先卡到某一年往往只剩一万多行,剩下的过滤在内存里做也很快。
为什么这样做:SQLite 默认每次插入都开一个事务并刷盘,批量场景下这部分开销远大于解析 CSV;而经纬度这种二维范围查询,单列索引帮不上太多忙,不如用时间这个天然有序的维度先砍一刀。代价是查询逻辑变复杂了一点,而且时间索引要求时间格式严格统一,混进 2023/5/1 这种写法就会漏数据。
会失效的地方:如果查询本身就是跨十年、跨全球的统计,先卡时间就没意义了,还是得全表扫;另外 REAL 存经纬度做等值比较会有精度问题,我踩过一次,同一个坐标查不出来,后来统一改成范围比较(lon > ? AND lon < ?)。还有,CSV 里空震级写成空字符串时,REAL 列会存成 NULL,mag > 4 会把这些行直接筛掉,这点得提前想清楚是不是你要的。
你们本地存地震目录时,是倾向于一个宽表加索引,还是把位置拆出来单独建表?我这边数据量还没到需要拆的程度,想看看更大的量级下怎么选。
0 条评论
还没有评论,写下第一条。