簡體   English   中英

提高SQL查詢性能

[英]Improve SQL query performance

我有三個表,用於存儲實際人員數據( person ),團隊( team )和條目( athlete )。 這三個表的模式為:

數據庫架構

每個團隊中可能有兩個或更多的運動員。

我正在嘗試創建一個查詢,以產生最頻繁的對,即兩人一組比賽的人。 我想出了以下查詢:

SELECT p1.surname, p1.name, p2.surname, p2.name, COUNT(*) AS freq
FROM person p1, athlete a1, person p2, athlete a2
WHERE
    p1.id = a1.person_id AND
    p2.id = a2.person_id AND
    a1.team_id = a2.team_id AND
    a1.team_id IN
          ( SELECT team.id
            FROM team, athlete
            WHERE team.id = athlete.team_id
            GROUP BY team.id
            HAVING COUNT(*) = 2 )
GROUP BY p1.id
ORDER BY freq DESC

顯然,這是一個資源消耗查詢。 有辦法改善嗎?

SELECT id
FROM team, athlete
WHERE team.id = athlete.team_id
GROUP BY team.id
HAVING COUNT(*) = 2

效果提示1:此處只需要athlete表。

您可能會考慮以下方法,該方法使用觸發器來維護團隊和人員表中的計數器,因此您可以輕松地找出哪些團隊有2個或更多運動員,哪些人員在2個或更多團隊中。

(注意:我從運動員表中刪除了替代ID密鑰,轉而使用了組合鍵,它將更好地實現數據完整性。我還將運動員重命名為team_athlete)

drop table if exists person;
create table person
(
person_id int unsigned not null auto_increment primary key,
name varchar(255) not null,
team_count smallint unsigned not null default 0
)
engine=innodb;

drop table if exists team;
create table team 
(
team_id int unsigned not null auto_increment primary key,
name varchar(255) not null,
athlete_count smallint unsigned not null default 0,
key (athlete_count) 
)
engine=innodb;

drop table if exists team_athlete;
create table team_athlete
(
team_id int unsigned not null,
person_id int unsigned not null,
primary key (team_id, person_id), -- note clustered composite PK
key person(person_id) -- added index
)
engine=innodb;

delimiter #

create trigger team_athlete_after_ins_trig after insert on team_athlete
for each row
begin
  update team set athlete_count = athlete_count+1 where team_id = new.team_id;
  update person set team_count = team_count+1 where person_id = new.person_id;
end#

delimiter ;

insert into person (name) values ('p1'),('p2'),('p3'),('p4'),('p5');
insert into team (name) values ('t1'),('t2'),('t3'),('t4');

insert into team_athlete (team_id, person_id) values
(1,1),(1,2),(1,3),
(2,3),(2,4),
(3,1),(3,5);

select * from team_athlete;
select * from person;
select * from team;

select * from team where athlete_count >= 2;
select * from person where team_count >= 2;

編輯

添加了以下內容作為最初被誤解的問題:

創建一個僅包含2個人的團隊的視圖。

drop view if exists teams_with_2_players_view;

create view teams_with_2_players_view as
select
 t.team_id,
 ta.person_id,
 p.name as person_name
from
 team t
inner join team_athlete ta on t.team_id = ta.team_id
inner join person p on ta.person_id = p.person_id
where
 t.athlete_count = 2;

現在,使用該視圖查找最常出現的人對。

select 
 p1.person_id as p1_person_id,
 p1.person_name as p1_person_name,
 p2.person_id as p2_person_id,
 p2.person_name as p2_person_name,
 count(*) as counter
from
 teams_with_2_players_view p1
inner join teams_with_2_players_view p2 on 
  p2.team_id = p1.team_id and p2.person_id > p1.person_id
group by
 p1.person_id, p2.person_id
order by
 counter desc;

希望這可以幫助 :)

編輯2檢查性能

select count(*) as counter from person;

+---------+
| counter |
+---------+
|   10000 |
+---------+
1 row in set (0.00 sec)

select count(*) as counter from team;

+---------+
| counter |
+---------+
|  450000 |
+---------+
1 row in set (0.08 sec)

select count(*) as counter from team where athlete_count = 2;

+---------+
| counter |
+---------+
|  112644 |
+---------+
1 row in set (0.03 sec)

select count(*) as counter from team_athlete;

+---------+
| counter |
+---------+
| 1124772 |
+---------+
1 row in set (0.21 sec)

explain
select 
 p1.person_id as p1_person_id,
 p1.person_name as p1_person_name,
 p2.person_id as p2_person_id,
 p2.person_name as p2_person_name,
 count(*) as counter
from
 teams_with_2_players_view p1
inner join teams_with_2_players_view p2 on 
  p2.team_id = p1.team_id and p2.person_id > p1.person_id
group by
 p1.person_id, p2.person_id
order by
 counter desc
limit 10;

+----+-------------+-------+--------+---------------------+-------------+---------+---------------------+-------+----------------------------------------------+
| id | select_type | table | type   | possible_keys       | key         | key_len | ref                 | rows  | Extra                                        |
+----+-------------+-------+--------+---------------------+-------------+---------+---------------------+-------+----------------------------------------------+
|  1 | SIMPLE      | t     | ref    | PRIMARY,t_count_idx | t_count_idx | 2  | const               | 86588 | Using index; Using temporary; Using filesort |
|  1 | SIMPLE      | t     | eq_ref | PRIMARY,t_count_idx | PRIMARY     | 4  | foo_db.t.team_id    |     1 | Using where                                  |
|  1 | SIMPLE      | ta    | ref    | PRIMARY,person      | PRIMARY     | 4  | foo_db.t.team_id    |     1 | Using index                                  |
|  1 | SIMPLE      | p     | eq_ref | PRIMARY             | PRIMARY     | 4  | foo_db.ta.person_id |     1 |                                              |
|  1 | SIMPLE      | ta    | ref    | PRIMARY,person      | PRIMARY     | 4  | foo_db.t.team_id    |     1 | Using where; Using index                     |
|  1 | SIMPLE      | p     | eq_ref | PRIMARY             | PRIMARY     | 4  | foo_db.ta.person_id |     1 |                                              |
+----+-------------+-------+--------+---------------------+-------------+---------+---------------------+-------+----------------------------------------------+

6 rows in set (0.00 sec)

select 
 p1.person_id as p1_person_id,
 p1.person_name as p1_person_name,
 p2.person_id as p2_person_id,
 p2.person_name as p2_person_name,
 count(*) as counter
from
 teams_with_2_players_view p1
inner join teams_with_2_players_view p2 on 
  p2.team_id = p1.team_id and p2.person_id > p1.person_id
group by
 p1.person_id, p2.person_id
order by
 counter desc
limit 10;

+--------------+----------------+--------------+----------------+---------+
| p1_person_id | p1_person_name | p2_person_id | p2_person_name | counter |
+--------------+----------------+--------------+----------------+---------+
|          221 | person 221     |          739 | person 739     |       5 |
|          129 | person 129     |          249 | person 249     |       5 |
|          874 | person 874     |          877 | person 877     |       4 |
|          717 | person 717     |          949 | person 949     |       4 |
|          395 | person 395     |          976 | person 976     |       4 |
|          415 | person 415     |          828 | person 828     |       4 |
|          287 | person 287     |          470 | person 470     |       4 |
|          455 | person 455     |          860 | person 860     |       4 |
|           13 | person 13      |           29 | person 29      |       4 |
|            1 | person 1       |          743 | person 743     |       4 |
+--------------+----------------+--------------+----------------+---------+
10 rows in set (2.02 sec)

在這里,一些提高SQL選擇查詢性能的技巧如下:

  • 使用SET NOCOUNT ON有助於減少網絡流量,從而提高性能。
  • 使用標准過程名稱(例如, database.schema.objectname
  • 使用sp_executesql代替execute動態查詢
  • 不要使用select *select column1,column2,..用於IF EXISTSSELECT操作
  • 避免像sp_procedureName這樣命名用戶存儲過程,因為如果使用存儲過程名稱以sp_則SQL首先在主數據庫中搜索。 因此會降低查詢性能。

應該避免附加約束a1.person_id!= a2.person_id以避免與同一位玩家創建一對嗎? 這可能不會影響結果的最終排序,但會影響計數的准確性。

如果可能的話,您可以在球隊表中添加一列名為“ sporter_count”(帶有索引)的列,每當將球員添加或刪除到球隊時都可以更新該列,這樣就可以避免需要遍歷整個運動員表來查找子查詢的子查詢。兩個球員團隊。

UPDATE1:另外,如果我正確地理解了原始查詢,那么按p1.id分組時,您只會得到兩名球員在兩人球隊中的出場次數,而不是這對球員的人數。 您可能需要對BY BY p1.id,p2.id進行分組。

基於每個團隊的兩次修訂

通過恰好兩個人的最內部預先聚集,我可以使用MIN()和MAX()將每個具有personA和PersonB的團隊分配到每個團隊的一行中。 這樣,此人的ID將始終處於高低配對設置中,以便將來的團隊進行比較。 然后,我可以在所有團隊中通過共同的Mate1和Mate2查詢COUNT,並直接獲取其名稱。

SELECT STRAIGHT_JOIN
      p1.surname, 
      p1.name, 
      p2.surname, 
      p2.name, 
      TeamAggregates.CommonTeams
   from 
     ( select PreQueryTeams.Mate1,
              PreQueryTeams.Mate2,
              count(*) CommonTeams
           from
              ( SELECT team_id, 
                       min( person_id ) mate1, 
                       max( person_id ) mate2
                   FROM 
                       athlete 
                   group by 
                       team_id 
                   having count(*) = 2 ) PreQueryTeams
           group by
              PreQueryTeams.Mate1,
              PreQueryTeams.Mate2  ) TeamAggregates,
      person p1,
      person p2
   where
          TeamAggregates.Mate1 = p1.Person_ID
      and TeamAggregates.Mate2 = p2.Person_ID
   order by 
      TeamAggregates.CommonTeams

任意數量的團隊的原始答案

我會通過以下方式來做。 內部預查詢首先加入每個團隊中所有可能的人員組合,但是使person1 <person2可以消除將與person1和person2相同的人員計數。

athlete   person   team
1         1        1   
2         2        1
3         3        1
4         4        1
5         1        2
6         3        2
7         4        2
8         1        3
9         4        3

So, from team 1 you would get person pairs of
1,2    1,3   1,4      2,3     2,4    3,4
and NOT get reversed duplicates such as 
2,1    3,1   4,1      3,2     4,2    4,3
nor same person
1,1    2,2   3,3   4,4 


Then from team 2, you would hav pairs of
1,3   1,4   3,4

Finally in team 3 the single pair of 
1,4

thus teammates 1,4 have occured in 3 common teams.

SELECT STRAIGHT_JOIN
      p1.surname, 
      p1.name, 
      p2.surname, 
      p2.name, 
      PreQuery.CommonTeams
   from 
      ( select
            a1.Person_ID Person_ID1,
            a2.Person_ID Person_ID2,
            count(*) CommonTeams
         from 
            athlete a1,
            athlete a2
         where
                a1.Team_ID = a2.Team_ID
            and a1.Person_ID < a2.Person_ID
         group by 
            1, 2
         having CommonTeams > 1 ) PreQuery,
      person p1,
      person p2
   where 
          PreQuery.Person_ID1 = p1.id
      and PreQuery.Person_ID2 = p2.id
   order by 
      PreQuery.CommonTeams

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM