加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

perl – 选择distinct比group by更快

发布时间:2020-12-16 06:13:29 所属栏目:大数据 来源:网络整理
导读:我想运行以下搜索: schema-resultset('Entity')-search({ -or = { "me.user_id" = $user_id,'set_to_user.user_id' = $user_id } },{ 'distinct' = 1,'join' = {'entity_to_set' = {'entity_set' = 'set_to_user'}},'order_by' = {'-desc' = 'modified'},'p
我想运行以下搜索:

schema->resultset('Entity')->search({ 
        -or => { "me.user_id" => $user_id,'set_to_user.user_id' => $user_id } 
    },{
        'distinct' => 1,'join' => {'entity_to_set' => {'entity_set' => 'set_to_user'}},'order_by' => {'-desc' => 'modified'},'page' => 1,'rows' => 100
    });

在包含表格的数据库中,如下所示.

CREATE TABLE entity (
  id varchar(500) NOT NULL,user_id varchar(100) NOT NULL,modified timestamp NOT NULL,PRIMARY KEY (id,user_id),FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE ON UPDATE CASCADE
);

CREATE TABLE entity_to_set (
  set_id varchar(100) NOT NULL,entity_id varchar(500) NOT NULL,PRIMARY KEY (set_id,user_id,entity_id),FOREIGN KEY (entity_id,user_id) REFERENCES entity(id,user_id) ON DELETE CASCADE ON UPDATE CASCADE,FOREIGN KEY (set_id) REFERENCES entity_set(id) ON DELETE CASCADE ON UPDATE CASCADE
);

CREATE TABLE entity_set (
  id varchar(100) NOT NULL,PRIMARY KEY (id)
);

CREATE TABLE set_to_user (
  set_id varchar(100) NOT NULL,FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE ON UPDATE CASCADE,FOREIGN KEY (set_id) REFERENCES entity_set(id) ON DELETE CASCADE ON UPDATE CASCADE
);

CREATE TABLE user (
  id varchar(100) NOT NULL,PRIMARY KEY (id)
);

我有大约6000个实体,6000个entity_to_set,10个entity_set和50个set_to_user.

现在,这个查询需要一些时间,(一两秒钟),这是不幸的.仅对实体表进行查询(包括ORDER BY)时,结果几乎是即时的.作为调试的第一步,我找到了DBIC代码变为的实际SQL查询:

SELECT me.id,me.user_id,me.modified FROM entity me
LEFT JOIN entity_to_set entity_to_set ON ( entity_to_set.entity_id = me.id AND entity_to_set.user_id = me.user_id ) 
LEFT JOIN entity_set entity_set ON entity_set.id = entity_to_set.set_id 
LEFT JOIN set_to_user set_to_user ON set_to_user.set_id = entity_set.id 
WHERE ( ( set_to_user.user_id = 'Craigy' OR me.user_id = 'Craigy' ) ) 
GROUP BY me.id,me.modified ORDER BY modified DESC LIMIT 100;

这是EXPLAIN QUERY PLAN的结果

0|0|0|SCAN TABLE entity AS me USING INDEX sqlite_autoindex_entity_1 (~1000000 rows)
0|1|1|SEARCH TABLE entity_to_set AS entity_to_set USING COVERING INDEX entity_to_set_idx_cover (entity_id=? AND user_id=?) (~9 rows)
0|2|2|SEARCH TABLE entity_set AS entity_set USING COVERING INDEX sqlite_autoindex_entity_set_1 (id=?) (~1 rows)
0|3|3|SEARCH TABLE set_to_user AS set_to_user USING COVERING INDEX sqlite_autoindex_set_to_user_1 (set_id=?) (~5 rows)
0|0|0|USE TEMP B-TREE FOR ORDER BY

其中entity_to_set_idx_cover是

CREATE INDEX entity_to_set_idx_cover ON entity_to_set (entity_id,set_id);

现在,问题是用于排序的b树,而不是在我不进行连接时使用的索引.

我注意到DBIx :: Class转换’distinct’=> 1进入GROUP BY语句(I believe the documentation says they are equivalent here).我删除了GROUP BY语句并改为使用SELECT DISTINCT,并使用以下查询

SELECT DISTINCT me.id,me.modified FROM entity me
LEFT JOIN entity_to_set entity_to_set ON ( entity_to_set.entity_id = me.id AND entity_to_set.user_id = me.user_id ) 
LEFT JOIN entity_set entity_set ON entity_set.id = entity_to_set.set_id 
LEFT JOIN set_to_user set_to_user ON set_to_user.set_id = entity_set.id 
WHERE ( ( set_to_user.user_id = 'Craigy' OR me.user_id = 'Craigy' ) ) 
ORDER BY modified DESC LIMIT 100;

我认为它给出了相同的结果.此查询的EXPLAIN QUERY PLAN是

0|0|0|SCAN TABLE entity AS me USING COVERING INDEX entity_sort_modified_user_id (~1000000 rows)
0|1|1|SEARCH TABLE entity_to_set AS entity_to_set USING COVERING INDEX entity_to_set_idx_cover (entity_id=? AND user_id=?) (~9 rows)
0|2|2|SEARCH TABLE entity_set AS entity_set USING COVERING INDEX sqlite_autoindex_entity_set_1 (id=?) (~1 rows)
0|3|3|SEARCH TABLE set_to_user AS set_to_user USING COVERING INDEX sqlite_autoindex_set_to_user_1 (set_id=?) (~5 rows)

其中entity_sort_modified_user_id是使用创建的索引

CREATE INDEX entity_sort_modified_user_id ON entity (modified,id);

这几乎是瞬间运行(没有b树).

编辑:为了证明当ORDER BY按升序排列时问题仍然存在,以及索引对这些查询的影响,这里是对相同表的类似查询.前两个查询分别使用SELECT DISTINCT和GROUP BY没有索引,后两个查询具有相同的查询和索引.

sqlite> EXPLAIN QUERY PLAN SELECT DISTINCT me.id,me.modified FROM entity me LEFT JOIN entity_to_set entity_to_set ON ( entity_to_set.entity_id = me.id AND entity_to_set.user_id = me.user_id ) LEFT JOIN entity_set entity_set ON entity_set.id = entity_to_set.set_id WHERE ( me.user_id = 'Craigy' AND entity_set.id = 'SetID' ) ORDER BY modified LIMIT 100;
0|0|0|SCAN TABLE entity AS me (~100000 rows)
0|1|1|SEARCH TABLE entity_to_set AS entity_to_set USING AUTOMATIC COVERING INDEX (entity_id=? AND user_id=?) (~7 rows)
0|2|2|SEARCH TABLE entity_set AS entity_set USING COVERING INDEX sqlite_autoindex_entity_set_1 (id=?) (~1 rows)
0|0|0|USE TEMP B-TREE FOR DISTINCT
0|0|0|USE TEMP B-TREE FOR ORDER BY
sqlite> EXPLAIN QUERY PLAN SELECT me.id,me.modified FROM entity me LEFT JOIN entity_to_set entity_to_set ON ( entity_to_set.entity_id = me.id AND entity_to_set.user_id = me.user_id ) LEFT JOIN entity_set entity_set ON entity_set.id = entity_to_set.set_id WHERE ( me.user_id = 'Craigy' AND entity_set.id = 'SetID' ) GROUP BY me.id,me.modified ORDER BY modified LIMIT 100;
0|0|0|SCAN TABLE entity AS me USING INDEX sqlite_autoindex_entity_1 (~100000 rows)
0|1|1|SEARCH TABLE entity_to_set AS entity_to_set USING AUTOMATIC COVERING INDEX (entity_id=? AND user_id=?) (~7 rows)
0|2|2|SEARCH TABLE entity_set AS entity_set USING COVERING INDEX sqlite_autoindex_entity_set_1 (id=?) (~1 rows)
0|0|0|USE TEMP B-TREE FOR ORDER BY
sqlite> CREATE INDEX entity_idx_user_id_modified_id ON entity (user_id,modified,id);
sqlite> EXPLAIN QUERY PLAN SELECT DISTINCT me.id,me.modified FROM entity me LEFT JOIN entity_to_set entity_to_set ON ( entity_to_set.entity_id = me.id AND entity_to_set.user_id = me.user_id ) LEFT JOIN entity_set entity_set ON entity_set.id = entity_to_set.set_id WHERE ( me.user_id = 'Craigy' AND entity_set.id = 'SetID' ) ORDER BY modified LIMIT 100;
0|0|0|SEARCH TABLE entity AS me USING COVERING INDEX entity_idx_user_id_modified_id (user_id=?) (~10 rows)
0|1|1|SEARCH TABLE entity_to_set AS entity_to_set USING AUTOMATIC COVERING INDEX (entity_id=? AND user_id=?) (~7 rows)
0|2|2|SEARCH TABLE entity_set AS entity_set USING COVERING INDEX sqlite_autoindex_entity_set_1 (id=?) (~1 rows)
sqlite> EXPLAIN QUERY PLAN SELECT me.id,me.modified ORDER BY modified LIMIT 100;
0|0|0|SEARCH TABLE entity AS me USING COVERING INDEX entity_idx_user_id_modified_id (user_id=?) (~10 rows)
0|1|1|SEARCH TABLE entity_to_set AS entity_to_set USING AUTOMATIC COVERING INDEX (entity_id=? AND user_id=?) (~7 rows)
0|2|2|SEARCH TABLE entity_set AS entity_set USING COVERING INDEX sqlite_autoindex_entity_set_1 (id=?) (~1 rows)
0|0|0|USE TEMP B-TREE FOR GROUP BY
0|0|0|USE TEMP B-TREE FOR ORDER BY

我的问题是:如何修复我的DBIx :: Class代码,使其执行与SELECT DISTINCT查询一样好.或者我如何添加索引以使其工作正常?还是需要一些其他类型的修复?

解决方法

注意:这不是这个问题的完整答案.它仅显示了在按升序排序时如何避免临时b树.当需要按降序排序时,AFAIK当前(版本3.8.1)没有办法(没有调整sqlite)以避免GROUP BY版本的临时b树.

使用问题中的表定义和索引:

sqlite> select sqlite_version();
sqlite_version()
----------------
3.8.1

当(a)ORDER BY按升序排列并且(b)GROUP BY子句逐列匹配ORDER BY子句时,查询将在没有临时b树的情况下运行.

除GROUP BY和ORDER BY子句外,查询保持不变:

/* table definitions as shown in the question */
sqlite> CREATE INDEX entity_to_set_idx_cover ON entity_to_set (entity_id,set_id);
sqlite> CREATE INDEX entity_sort_modified_user_id ON entity (modified,id);

sqlite> EXPLAIN QUERY PLAN
   ...> SELECT  me.id,me.modified FROM entity me
   ...> LEFT JOIN entity_to_set entity_to_set ON ( entity_to_set.entity_id = me.id AND entity_to_set.user_id = me.user_id )
   ...> LEFT JOIN entity_set entity_set ON entity_set.id = entity_to_set.set_id
   ...> LEFT JOIN set_to_user set_to_user ON set_to_user.set_id = entity_set.id
   ...> WHERE ( ( set_to_user.user_id = 'Craigy' OR me.user_id = 'Craigy' ) )
   ...> GROUP BY me.modified,me.id
   ...> ORDER BY me.modified,me.id ASC LIMIT 100;

selectid    order       from        detail
----------  ----------  ----------  -------------------------------------------------------------------------
0           0           0           SCAN TABLE entity AS me USING COVERING INDEX entity_sort_modified_user_id
0           1           1           SEARCH TABLE entity_to_set AS entity_to_set USING COVERING INDEX entity_t
0           2           2           SEARCH TABLE entity_set AS entity_set USING COVERING INDEX sqlite_autoind
0           3           3           SEARCH TABLE set_to_user AS set_to_user USING COVERING INDEX sqlite_autoi

但是,当您按降序排序ORDER BY时,会得到一个临时b树:

...> ...
   ...> GROUP BY me.modified,me.id DESC LIMIT 100;
selectid    order       from        detail
----------  ----------  ----------  -------------------------------------------------------------------------
0           0           0           SCAN TABLE entity AS me USING COVERING INDEX entity_sort_modified_user_id
0           1           1           SEARCH TABLE entity_to_set AS entity_to_set USING COVERING INDEX entity_t
0           2           2           SEARCH TABLE entity_set AS entity_set USING COVERING INDEX sqlite_autoind
0           3           3           SEARCH TABLE set_to_user AS set_to_user USING COVERING INDEX sqlite_autoi
0           0           0           USE TEMP B-TREE FOR ORDER BY

原因是sqlite(直到当前版本的3.8.1)无法识别它可以按降序进行分组.因此,您将始终获得单独的步骤.即使将索引声明为DESC,也无法避免这种情况.请参阅有关sqlite mailing list的讨论.

结论如果您希望查询ORDER BY DESC而不使用临时b树,则必须调整SQL生成以使用DISTINCT.

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读