Frage

Die Box Diese Abfrage ausgeführt wird, ein dedizierter Server in einem Rechenzentrum ausgeführt wird.

AMD Opteron 1354 Quad-Core 2.20GHz 2 GB RAM Windows Server 2008 x64 (Ja, ich weiß, dass ich nur 2 GB RAM haben, ich bin zu 8 GB Upgrade, wenn das Projekt live geht).

Also ich durchgemacht habe und erstellt 250000 Dummy-Zeilen in einer Tabelle wirklich betonen, einige Abfragen testen, dass LINQ to SQL generiert und stellen Sie sicher, sie sind nicht zu schrecklich, und ich bemerkte einer von ihnen eine absurde Menge an Zeit nahm.

Ich hatte diese Abfrage nach unten bis 17 Sekunden mit Indizes, aber ich tut sie für die Zwecke dieser Antwort von Anfang bis Ende zu gehen. Nur Indizes sind Primärschlüssel.

Stories table --
[ID] [int] IDENTITY(1,1) NOT NULL,
[UserID] [int] NOT NULL,
[CategoryID] [int] NOT NULL,
[VoteCount] [int] NOT NULL,
[CommentCount] [int] NOT NULL,
[Title] [nvarchar](96) NOT NULL,
[Description] [nvarchar](1024) NOT NULL,
[CreatedAt] [datetime] NOT NULL,
[UniqueName] [nvarchar](96) NOT NULL,
[Url] [nvarchar](512) NOT NULL,
[LastActivityAt] [datetime] NOT NULL,

Categories table --
[ID] [int] IDENTITY(1,1) NOT NULL,
[ShortName] [nvarchar](8) NOT NULL,
[Name] [nvarchar](64) NOT NULL,

Users table --
[ID] [int] IDENTITY(1,1) NOT NULL,
[Username] [nvarchar](32) NOT NULL,
[Password] [nvarchar](64) NOT NULL,
[Email] [nvarchar](320) NOT NULL,
[CreatedAt] [datetime] NOT NULL,
[LastActivityAt] [datetime] NOT NULL,

Zur Zeit in der Datenbank gibt es 1 Benutzer, 1 Kategorie und 250.000 Geschichten, und ich versuchte, diese Abfrage auszuführen.

SELECT TOP(10) *
FROM Stories
INNER JOIN Categories ON Categories.ID = Stories.CategoryID
INNER JOIN Users ON Users.ID = Stories.UserID
ORDER BY Stories.LastActivityAt

Abfrage 52 Sekunden dauert laufen, schwebt die CPU-Auslastung bei 2-3%, ist Membery 1,1 GB, 900 MB frei, aber die Plattenbelegung scheint außer Kontrolle geraten. Es ist @ 100 MB / s mit 2/3 des Seins zu tempdb.mdf schreibt und der Rest aus tempdb.mdf liest.

Nun zum interessanten Teil ...

SELECT TOP(10) *
FROM Stories
INNER JOIN Categories ON Categories.ID = Stories.CategoryID
INNER JOIN Users ON Users.ID = Stories.UserID

SELECT TOP(10) *
FROM Stories
INNER JOIN Users ON Users.ID = Stories.UserID
ORDER BY Stories.LastActivityAt

SELECT TOP(10) *
FROM Stories
INNER JOIN Categories ON Categories.ID = Stories.CategoryID
ORDER BY Stories.LastActivityAt

Alle 3 dieser Abfragen sind so ziemlich sofort.

Exec Plan für die erste Abfrage.
http://i43.tinypic.com/xp6gi1.png

Exec Pläne für andere 3 Abfragen (in dieser Reihenfolge).
http://i43.tinypic.com/30124bp.png
http://i44.tinypic.com/13yjml1.png
http://i43.tinypic.com/33ue7fb.png

Jede Hilfe wäre sehr geschätzt werden.

Exec Plan Indizes nach der Zugabe von (bis zu 17 Sekunden wieder).
http://i39.tinypic.com/2008ytx.png

Ich habe eine Menge von hilfreichem Feedback von allen bekommen, und ich danke, habe ich versucht, einen neuen Blickwinkel auf diese. Ich abfragen, um die Geschichten, die ich brauche, dann in separaten Abfragen, die Kategorien und Benutzer erhalten und mit 3 Anfragen dauerte es nur 250ms ... Ich weiß nicht, das Problem verstehen, aber wenn es funktioniert und bei 250ms nicht weniger für die Zeit, ich werde sein mit dem Stick. Hier ist der Code, den ich verwenden, um dies zu testen.

DBDataContext db = new DBDataContext();
Console.ReadLine();

Stopwatch sw = Stopwatch.StartNew();

var stories = db.Stories.OrderBy(s => s.LastActivityAt).Take(10).ToList();
var storyIDs = stories.Select(c => c.ID);
var categories = db.Categories.Where(c => storyIDs.Contains(c.ID)).ToList();
var users = db.Users.Where(u => storyIDs.Contains(u.ID)).ToList();

sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
War es hilfreich?

Lösung

Versuchen Sie einen Index für Stories.LastActivityAt hinzufügen. Ich denke, dass der gruppierte Index Scan in dem Ausführungsplan, um die Sortierung zurückgeführt werden kann.

Edit: Da meine Abfrage in einem Augenblick mit Reihen nur wenige Bytes lang zurück, aber für 5 Minuten wurde bereits ausgeführt und ist immer noch, nachdem ich ein 2K varchar hinzugefügt, ich denke, Mitch hat einen Punkt. Es ist das Volumen der Daten, die für nichts schlurfte um ist, aber dies kann in der Abfrage festgelegt werden.

Versuchen Sie, die join setzen, zu sortieren und nach oben (10) in einer Ansicht oder in einer verschachtelten Abfrage, und dann wieder gegen die Geschichte Tisch kommt, den Rest der Daten zu erhalten, nur für die 10 Zeilen, die Sie benötigen.

Wie folgt aus:

select * from 
(
    SELECT TOP(10) id, categoryID, userID
    FROM Stories
    ORDER BY Stories.LastActivityAt
) s
INNER JOIN Stories ON Stories.ID = s.id
INNER JOIN Categories ON Categories.ID = s.CategoryID
INNER JOIN Users ON Users.ID = s.UserID

Wenn Sie einen Index auf LastActivityAt haben, sollten diese sehr schnell laufen.

Andere Tipps

Also, wenn ich den ersten Teil richtig gelesen, antwortet er in 17 Sekunden mit einem Index. Das ist immer noch eine Weile 10 Datensätze tuckern aus. Ich denke, die Zeit, in der durch Klausel ist. Ich würde einen Index für LastActivityAt, UserID, CategoryID wollen. Just for fun, entfernen Sie die Bestellung durch und sehen, ob es die 10 Datensätze schnell zurückgibt. Ist dies der Fall, dann wissen Sie, es ist nicht in der zu den anderen Tabellen verknüpft. Auch wäre es hilfreich sein, die * mit den Spalten zu ersetzen benötigt, da alle drei Tabellenspalten in der Tempdb sind wie Sie sortieren -. Wie Neil erwähnte

bei der Ausführung der Suche Pläne werden Sie die zusätzliche Art bemerken - ich glaube, dass die Reihenfolge, mit der einige Zeit in Anspruch nehmen wird. Ich gehe davon aus Sie einen Index mit dem 3 hatte und es war 17 Sekunden ... so können Sie einen Index für die Join-Kriterien (Benutzer-ID, categoryID) und ein anderes für lastactivityat wollen - sehen, ob das besser abschneidet. Auch wäre es gut, die Abfrage durch die Indexoptimierungs-Assistenten auszuführen.

Mein erster Vorschlag ist, den * zu entfernen und ersetzen Sie ihn durch den minimalen Spalten Sie benötigen.

zweitens gibt es einen Auslöser beteiligt? Etwas, das das LastActivityAt Feld aktualisieren würde?

Auf der Basis Ihrer Problem Abfrage, versuchen Sie einen Kombinationsindex auf dem Tisch Stories hinzufügen (CategoryID, UserID, LastActivityAt)

Sie sind maxing die Disks in Ihrer Hardware-Konfiguration aus.

Da Ihre Kommentare über Ihre Daten / Log / tempDB Datei Platzierung, ich glaube, jede Menge Tuning wird ein bandaid sein.

250.000 Zeilen klein ist. Stellen Sie sich vor, wie schlecht Ihre Probleme mit 10 Millionen Zeilen sein werden.

Ich schlage vor, Sie tempDB auf sein eigenen physikalischen Laufwerk (vorzugsweise ein RAID 0) zu bewegen.

Ok, so meine Testmaschine ist nicht schnell. Eigentlich ist es sehr langsam. Es 1.6 ghz, n 1 GB RAM, keine mehrere Festplatten, nur eine einzige (lesen Sie langsam) Platte für SQL Server, Betriebssystem und Extras.

Ich habe Ihre Tabellen mit Primär- und Fremdschlüssel definiert. Eingefügt 2 Kategorien, 500 zufällige Benutzer und 250000 zufällige Geschichten.

Ausführen die erste Abfrage dauert über 16 Sekunden (kein Plan Cache entweder). Wenn ich Index der LastActivityAt Spalte I Ergebnisse erhalten in weniger als einer Sekunde (ohne Plan Cache hier entweder).

Hier ist das Skript, das ich verwenden, um all dies zu tun.

    --Categories table --
Create table Categories (
[ID] [int] IDENTITY(1,1) primary key NOT NULL,
[ShortName] [nvarchar](8) NOT NULL,
[Name] [nvarchar](64) NOT NULL)

--Users table --
Create table Users(
[ID] [int] IDENTITY(1,1) primary key NOT NULL,
[Username] [nvarchar](32) NOT NULL,
[Password] [nvarchar](64) NOT NULL,
[Email] [nvarchar](320) NOT NULL,
[CreatedAt] [datetime] NOT NULL,
[LastActivityAt] [datetime] NOT NULL
)
go

-- Stories table --
Create table Stories(
[ID] [int] IDENTITY(1,1) primary key NOT NULL,
[UserID] [int] NOT NULL references Users ,
[CategoryID] [int] NOT NULL references Categories,
[VoteCount] [int] NOT NULL,
[CommentCount] [int] NOT NULL,
[Title] [nvarchar](96) NOT NULL,
[Description] [nvarchar](1024) NOT NULL,
[CreatedAt] [datetime] NOT NULL,
[UniqueName] [nvarchar](96) NOT NULL,
[Url] [nvarchar](512) NOT NULL,
[LastActivityAt] [datetime] NOT NULL)

Insert into Categories (ShortName, Name) 
Values ('cat1', 'Test Category One')

Insert into Categories (ShortName, Name) 
Values ('cat2', 'Test Category Two')

--Dummy Users
Insert into Users
Select top 500
UserName=left(SO.name+SC.name, 32)
, Password=left(reverse(SC.name+SO.name), 64)
, Email=Left(SO.name, 128)+'@'+left(SC.name, 123)+'.com'
, CreatedAt='1899-12-31'
, LastActivityAt=GETDATE()
from sysobjects SO 
Inner Join syscolumns SC on SO.id=SC.id
go

--dummy stories!
-- A Count is given every 10000 record inserts (could be faster)
-- RBAR method!
set nocount on
Declare @count as bigint
Set @count = 0
begin transaction
while @count<=250000
begin
Insert into Stories
Select
  USERID=floor(((500 + 1) - 1) * RAND() + 1)
, CategoryID=floor(((2 + 1) - 1) * RAND() + 1)
, votecount=floor(((10 + 1) - 1) * RAND() + 1)
, commentcount=floor(((8 + 1) - 1) * RAND() + 1)
, Title=Cast(NEWID() as VARCHAR(36))+Cast(NEWID() as VARCHAR(36))
, Description=Cast(NEWID() as VARCHAR(36))+Cast(NEWID() as VARCHAR(36))+Cast(NEWID() as VARCHAR(36))
, CreatedAt='1899-12-31'
, UniqueName=Cast(NEWID() as VARCHAR(36))+Cast(NEWID() as VARCHAR(36)) 
, Url=Cast(NEWID() as VARCHAR(36))+Cast(NEWID() as VARCHAR(36))
, LastActivityAt=Dateadd(day, -floor(((600 + 1) - 1) * RAND() + 1), GETDATE())
If @count % 10000=0
Begin
Print @count
Commit
begin transaction
End
Set @count=@count+1
end 
set nocount off
go

--returns in 16 seconds
DBCC DROPCLEANBUFFERS
SELECT TOP(10) *
FROM Stories
INNER JOIN Categories ON Categories.ID = Stories.CategoryID
INNER JOIN Users ON Users.ID = Stories.UserID
ORDER BY Stories.LastActivityAt
go

--Now create an index
Create index IX_LastADate on Stories (LastActivityAt asc)
go
--With an index returns in less than a second
DBCC DROPCLEANBUFFERS
SELECT TOP(10) *
FROM Stories
INNER JOIN Categories ON Categories.ID = Stories.CategoryID
INNER JOIN Users ON Users.ID = Stories.UserID
ORDER BY Stories.LastActivityAt
go

Die Art ist definitiv, wo Ihre verlangsamen auftritt. Sortierung hauptsächlich wird in der Tempdb und eine große Tabelle verursacht viel zu tun gegeben werden. einen Index auf dieser Spalte hat, wird auf jeden Fall die Leistung verbessern auf einen Auftrag durch.

Auch die Definition Ihrer Primär- und Fremdschlüssel hilft SQL Server immensly

Ihre Methode, die im Code aufgeführt ist elegant, und im Grunde die gleiche Antwort, die cdonner außer in c # geschrieben und nicht SQL. Tuning der db wird wahrscheinlich geben noch bessere Ergebnisse!

- Kris

Haben Sie den SQL Server-Cache gelöscht, bevor jeder der Abfrage ausgeführt wird?

In SQL 2000, dann ist es so etwas wie DBCC DROPCLEANBUFFERS. Google der Befehl für weitere Informationen.

bei der Abfrage der Suche, ich würde für einen Index

Categories.ID Stories.CategoryID Users.ID Stories.UserID

und möglicherweise Stories.LastActivityAt

Aber ja, klingt das Ergebnis falscher ‚cos Caching sein könnte.

Wenn Sie mit SQL Server für einige Zeit gearbeitet haben, werden Sie feststellen, dass selbst die kleinsten Änderungen an einer Abfrage können völlig unterschiedliche Reaktionszeiten führen. Von dem, was ich in der Anfangs Frage gelesen haben, und an der Abfrage-Plan suchen, vermute ich, dass der Optimierer, dass der beste Ansatz ein Teilergebnis zu bilden, und dann, dass als separater Schritt sortieren entschieden hat. Das Teilergebnis ist ein Verbund der Benutzer und Geschichten Tabellen. Dies ist in tempdb gebildet. So ist der übermäßige Plattenzugriff ist auf die Bildung und dann von dieser temporären Tabelle zu sortieren.

ich einig, dass die Lösung sollte eine Verbindung Index auf Stories.LastActivityAt, Stories.UserId, Stories.CategoryId zu schaffen sein. Die Reihenfolge ist sehr wichtig, muss das Feld LastActivityAt ersten sein.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top