Как почти все вопросы производительности SQL, ответ не запрос, ответ схема данных. Какие индексы у вас есть, это то, что влияет на производительность ваших запросов.
Обычно отношения «многие ко многим» требуют двух дополнительных индексов: один - (ID1, ID2)
, а другой - (ID2, ID1)
. Один из них сгруппирован, на самом деле не имеет значения, какой из них. Так позволяет создать тестовую БД (100k статьи, 1K теги, 1-10 теги на статью):
:setvar dbname testdb
:setvar articles 1000000
:setvar tags 1000
:setvar articletags 10
:on error exit
set xact_abort on;
go
use master;
go
if db_id('$(dbname)') is not null
begin
alter database [$(dbname)] set single_user with rollback immediate;
drop database [$(dbname)];
end
go
create database [$(dbname)];
go
use [$(dbname)];
go
create TABLE Articles (
ArtID INT not null identity(1,1),
name varchar(100) not null,
filler char(500) not null default replicate('X', 500),
constraint pk_Articles primary key clustered (ArtID));
go
create table Tags (
TagID INT not null identity(1,1),
TagText VARCHAR(10) not null,
constraint pk_Tags primary key clustered (TagID),
constraint unq_Tags_Text unique (TagText));
go
create TABLE ArticleTags (
ArtID INT not null,
TagID INT not null,
constraint fk_Articles
foreign key (ArtID)
references Articles (ArtID),
constraint fk_Tags
foreign key (TagID)
references Tags (TagID),
constraint pk_ArticleTags
primary key clustered (ArtID, TagID));
go
create nonclustered index ndxArticleTags_TagID
on ArticleTags (TagID, ArtID);
go
-- populate articles
set nocount on;
declare @i int =0, @name varchar(100);
begin transaction
while @i < $(articles)
begin
set @name = 'Name ' + cast(@i as varchar(10));
insert into Articles (name) values (@name);
set @i += 1;
if @i %1000 = 0
begin
commit;
raiserror (N'Inserted %d articles', 0, 1, @i);
begin transaction;
end
end
commit
go
-- populate tags
set nocount on;
declare @i int =0, @text varchar(100);
begin transaction
while @i < $(tags)
begin
set @text = 'Tag ' + cast(@i as varchar(10));
insert into Tags (TagText) values (@text);
set @i += 1;
if @i %1000 = 0
begin
commit;
raiserror (N'Inserted %d tags', 0, 1, @i);
begin transaction;
end
end
commit
go
-- populate article-tags
set nocount on;
declare @i int =0, @a int = 1, @cnt int, @tag int;
set @cnt = rand() * $(articletags) + 1;
set @tag = rand() * $(tags) + 1;
begin transaction
while @a < $(articles)
begin
insert into ArticleTags (ArtID, TagID) values (@a, @tag);
set @cnt -= 1;
set @tag += rand()*10+1;
if $(tags)<[email protected]
begin
set @tag = 1;
end
if @cnt = 0
begin
set @cnt = rand() * $(articletags) + 1;
set @tag = rand() * $(tags) + 1;
set @a += 1;
end
set @i += 1;
if @i %1000 = 0
begin
commit;
raiserror (N'Inserted %d article-tags', 0, 1, @i);
begin transaction;
end
end
commit
raiserror (N'Final: %d article-tags', 0, 1, @i);
go
Теперь давайте сравним два запроса:
set statistics io on;
set statistics time on;
select a.ArtID
from Articles a
where exists (
select *
from ArticleTags at
join Tags t on at.TagID = t.TagID
where at.ArtID = a.ArtID
and t.TagText in ('Tag 10', 'Tag 12'));
SELECT a.ArtID FROM Articles a
INNER JOIN ArticleTags at ON a.ArtID = at.ArtID
INNER JOIN Tags t ON at.TagID = t.TagID
WHERE t.TagText IN ('Tag 10', 'Tag 12')
GROUP BY a.ArtID
Результат:
Table 'Articles'. Scan count 0, logical reads 3561, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'ArticleTags'. Scan count 2, logical reads 13, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Tags'. Scan count 2, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Articles'. Scan count 0, logical reads 3561, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'ArticleTags'. Scan count 2, logical reads 13, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Tags'. Scan count 2, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Сюрприз! (Ну не совсем). Это IDENTICAL. Фактически, они имеют точно такой же план выполнения.
Взгляните на планы запросов для обоих. Это должно сказать вам, какой из них лучше для вашей конкретной ситуации. – Oded
Установили ли вы полный текстовый поиск по tagText? – Patrick
Вы должны перейти к этому вопросу SO, http://stackoverflow.com/questions/1200295/sql-join-vs-in-performance, и взглянуть на ответ @Quassnoi.И затем вы можете перейти к ссылке, которую он предоставил для очень подробного и подробного объяснения и примеров этого поведения. Http: //explainextended.com/2009/06/16/in-vs-join-vs-exists/ – Lamak