老李分享:大数据性能调优案例1、“空间换时间”以及“内存中处理数据”

比如user_id.csv文件中有20万个不同的user_id,根据user_id去查其对应的用户最近发表的一篇帖子,取出post_id,post_title、post_time和user_id(post表中查,post表中有一列是user_id,表示帖子所属者),而帖子数目有大概两百万,那么如何处理呢?
我的解决方案是:
A. 先将post表post_id,post_title、post_time和user_id这四列导出到posts.csv文件中,然后利用一个CSV读取组件将posts.csv文件中的记录读取到csvRecords中
B. 然后利用“空间换时间的思想”,先将user_id.csv中的user_id读取到userIdList的List对象中,然后再将userIdList转为字典:
var userIdDict = userIdList.Distinct().ToDictionary(c => c, c => 1);
C. 最后比对userIdDict和csvRecords得到结果:
var resultRecords = csvRecords.Where(c => postDict.ContainsKey(c.UserId)).ToList(); // 这里ContainsKey的查询时间复杂度为O(1)

2、JOIN优化查询性能
一个页面查询效率非常低(一分多钟都没有结果),查询过程后台执行了3条SQL,其中2条SQL的执行时间都在39秒左右(2条SQL类似),造成数据库连接超时。
后台数据库查询用的是EF框架,而EF框架使用不当的话很容易导致糟糕的查询性能。
简单地模拟下(数据库表名已调整,记录数也有所改动,不影响结果):
用2张表:一张表为账户表比如account,记录数大概为3000条。另外一张表比如为帖子表post,记录数大概为190万。
然后后台处理过程大概是:先根据查询条件取得账户表的account_id的列表accountIds,然后再根据account_id列表去找post记录(post表有一个字段为account_id),大概这样:
var posts= db.Posts.Where(m).Where(c => accountIds.Contains(c.AccountId));
不需要过于关注这行代码,我用工具监测到这行代码对应的SQL为:
SELECT * from post
WHERE
((20 = account_id) OR (21 = account_id)) OR ...
ORDER BY created_at DESC
LIMIT 0, 15;
上面的OR条件大概有2000多个。EXPLAIN结果显示ROWS值达到134万多,也没有使用索引。
因为多了ORDER BY子句造成效率低,但是不能为了优化而省掉它,因为业务要求第一页显示最近的15篇帖子。
用JOIN查询,自己手工拼了一条SQL,发现几乎瞬间出结果,SQL大概如下:
SELECT * FROM 
posts AS A
JOIN
accounts AS B
ON A.account_id = B.id
WHERE A.category = 1 # 帖子的分类
ORDER BY A.created_at DESC
LIMIT 0, 15;
按照这个方法,舍弃EF,改用拼SQL的方法重写代码了。速度改进不少,原来需要近2分钟才能显示查询结果,现在只需要3-4秒。
这个场景用JOIN能提高查询效率,是因为一张表的记录数只有3000条左右,而另外一张有几百万的数据。如果两张表的数据都有几百万,那用JOIN未必能提高查询效率

3、业务逻辑代码层面优化
理解透彻业务逻辑,去除冗余业务逻辑代码