今天筛选爬虫失败的任务的时候,要求出一个子集的补集,使用not in语句效率太慢,后来通过left join实现了性能优化。
简朴的想法
今有一爬虫,每次从tasks
中取出任务,将结果存储在materials
数据表中,在完成时将tasks
对应finished
列的值从0变成1. 有时出现非HTTP错误时虽然finished
列的值是1,但是任务并没有完成,也就是说materials
表里并没有对应数据。
为了找出所有没有正常完成的任务,现构造sql语句如下:
select * from tasks where id not in ( select a.id from tasks a join materials b on a.url = b.url );
|
不那么显然地,这句话执行巨慢,3秒没有反应就被我强行终止了(ctrl + c)。有趣的是,如果上面的where not in
改为where in
语句,效率是正常的。具体的原理感兴趣者可以bing一下。
为了解决这个问题,稍微查阅以下资料,我决定使用left join来代替原本的where not in
语句。(mysql并没有except语句)
select * from tasks left join ( select * from tasks a join materials b on a.url = b.url ) c on tasks.id = c.id where c.url is NULL
|
不幸的是,执行依旧很慢... 通过一番简单的分析,推测子语句会执行很多次,所以拖慢了执行速度,原因和where not in
是一样的。
于是,稍微变化一下了的最终方案如下:
create temporary table `tmp` ( `id` int NOT NULL, `url` varchar(256) NOT NULL, `finished` tinyint(1) DEFAULT '0', `created_at` datetime DEFAULT NULL, `updated_at` datetime DEFAULT NULL, `deleted_at` datetime DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_tasks_deleted_at` (`deleted_at`) );
insert into tmp select distinct a.* from tasks a join materials b on a.url = b.url;
create temporary table `list` (`id` int, PRIMARY KEY(`id`));
insert into list select tasks.id from tasks left join tmp on tasks.id = tmp.id where tmp.id is NULL;
update tasks set finished = 0 where id in ( select id from list );
|