2009-03-04 14 views
14

यह क्वेरी चालू है यह बॉक्स एक डेटासेंटर में चल रहा एक समर्पित सर्वर है।250k पंक्तियों के खिलाफ 250 सेकंड की पंक्तियों के खिलाफ क्वेरी

AMD Opteron 1354 क्वाड-कोर 2.20GHz रैम विंडोज सर्वर 2008 64 के 2GB (हाँ मैं जानता हूँ कि मैं केवल 2GB RAM के है, मैं 8 जीबी के उन्नयन कर रहा हूँ जब परियोजना का सीधा प्रसारण होता)।

तो मैंने एक टेबल में 250,000 डमी पंक्तियां बनाईं और वास्तव में कुछ प्रश्नों का परीक्षण करने के लिए परीक्षण किया कि LINQ से SQL उत्पन्न होता है और सुनिश्चित करता है कि वे भयानक नहीं हैं और मैंने देखा कि उनमें से एक बेतुका समय ले रहा था।

मेरे पास यह क्वेरी इंडेक्स के साथ 17 सेकंड तक थी लेकिन मैंने उन्हें इस उत्तर के लिए शुरुआत से समाप्त होने के लिए हटा दिया। केवल अनुक्रमणिका प्राथमिक कुंजी हैं।

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, 

वर्तमान में डेटाबेस में 1 उपयोगकर्ता, 1 श्रेणी और 250,000 कहानियां हैं और मैंने इस क्वेरी को चलाने का प्रयास किया।

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 

क्वेरी को चलाने के लिए, CPU उपयोग 2-3% पर घूमता है 52 सेकंड लेता है, Membery 1.1GB, 900MB के लिए स्वतंत्र है, लेकिन डिस्क उपयोग नियंत्रण से बाहर लगता है। यह @ 100 एमबी/सेकंड है जिसमें से 2/3 tempdb.mdf पर लिख रहे हैं और बाकी tempdb.mdf से पढ़ रहे हैं।

अब दिलचस्प भाग के लिए ...

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 

सभी 3 इन खोजों के काफी तत्काल कर रहे हैं।

पहली क्वेरी के लिए Exec योजना।
http://i43.tinypic.com/xp6gi1.png

एक्ज़ेक अन्य 3 प्रश्नों (क्रम में) के लिए योजनाएं।
http://i43.tinypic.com/30124bp.png
http://i44.tinypic.com/13yjml1.png
http://i43.tinypic.com/33ue7fb.png

किसी भी मदद की बहुत सराहना की जाएगी।

इंडेक्स जोड़ने के बाद एक्सेल योजना (फिर से 17 सेकंड तक)।
http://i39.tinypic.com/2008ytx.png

मुझे हर किसी से बहुत उपयोगी प्रतिक्रिया मिली है और मैं आपको धन्यवाद देता हूं, मैंने इस पर एक नया कोण कोशिश की। मैं उन कहानियों से पूछता हूं जिनकी मुझे ज़रूरत है, फिर अलग-अलग प्रश्नों में श्रेणियां और उपयोगकर्ता मिलते हैं और 3 प्रश्नों के साथ ही इसे केवल 250 मिलीमीटर मिलते हैं ... मुझे इस मुद्दे को समझ में नहीं आता है, लेकिन अगर यह काम करता है और 250 एमएमएस पर कम समय के लिए मैं कम नहीं करता उस के साथ छड़ी। यहां वह कोड है जिसका उपयोग मैंने परीक्षण करने के लिए किया था।

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); 
+0

सुझाव है कि आप निष्पादन योजनाएं पोस्ट करें ... –

+0

आपका tempDB कहां स्थित है? क्या यह एक अलग शारीरिक धुरी पर है? –

+0

आपका डेटा कहां है, लॉग फाइलें? अलग भौतिक ड्राइव पर? –

उत्तर

13

कहानियों पर एक अनुक्रमणिका जोड़ने का प्रयास करें। LastActivityAt। मुझे लगता है कि निष्पादन योजना में क्लस्टर इंडेक्स स्कैन सॉर्टिंग के कारण हो सकता है।

संपादित करें: के बाद से मेरी क्वेरी पंक्तियों बस कुछ बाइट्स लंबे साथ एक पल में लौट आए, लेकिन 5 मिनट के लिए पहले से ही चल रहा है और बाद मैं एक 2K varchar जोड़ा अभी भी जा रहा है, मुझे लगता है कि मिच एक बिंदु है। यह उस डेटा की मात्रा है जो कुछ भी नहीं के लिए चारों ओर घूमती है, लेकिन इसे क्वेरी में ठीक किया जा सकता है।

किसी दृश्य में या नेस्टेड क्वेरी में शामिल होने, क्रमबद्ध और शीर्ष (10) डालने का प्रयास करें, और उसके बाद केवल 10 पंक्तियों के लिए शेष डेटा प्राप्त करने के लिए कहानी तालिका के विरुद्ध शामिल हों।

इस तरह

:

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 

आप LastActivityAt पर एक सूचकांक है, तो यह बहुत तेजी से चलाना चाहिए।

+0

कुल निष्पादन समय \t 25,0000 आप आदमी! –

+0

अच्छा है। मुझे आश्चर्य है कि ऑप्टिमाइज़र उस अनुकूलन को निष्पादित नहीं कर सकता है। –

+0

+1। विडंबना यह है कि मैंने SQL 2008 पर एक प्रश्न के लिए हाल ही में कुछ बहुत ही परिचित किया है, लेकिन यह मेरे लिए कभी नहीं हुआ कि यह एक ही समस्या हो सकती है !! –

1

मेरा पहला सुझाव * को हटाने और इसे आवश्यक न्यूनतम कॉलम के साथ प्रतिस्थापित करना है।

दूसरा, क्या कोई ट्रिगर शामिल है? कुछ ऐसा जो LastActivityAt फ़ील्ड को अपडेट करेगा?

+0

दाएं, LINQ से SQL के साथ यह वास्तव में स्तंभों को चुनने के लिए एक दर्द है। मैं पूरी तरह से समझता हूं कि यह संभव है लेकिन मैंने पिछली नौकरियों में ओआरएम का उपयोग किया है जो लाखों पंक्तियों वाले टेबल पर SELECT * का उपयोग करता था। मैं समझता हूं कि यह बुरा है लेकिन मैंने इस तरह के प्रदर्शन के नुकसान को कभी नहीं देखा है। –

+0

या, स्तंभों को सीमित करना अंधेरे में एक प्रकार का स्टैब था, और अधिक नहीं, मैं इंडेक्स के साथ खेलने के अलावा और भी आ सकता था। –

1

आपकी समस्या क्वेरी के आधार पर, टेबल Stories (CategoryID, UserID, LastActivityAt)

+0

चयन करने के लिए * –

+0

हाँ, यह 1 मिनट 2 सेकंड के लिए छोड़, प्रयोग नहीं किया जा सकता है कारण है। –

3

तो अगर मैं पहले भाग को सही ढंग से पढ़ें, यह एक सूचकांक के साथ 17 सेकंड में प्रतिक्रिया करता है पर एक संयोजन सूचकांक जोड़ने का प्रयास करें। 10 रिकॉर्ड आउट करने के लिए अभी भी कुछ समय है। मैं सोच रहा हूं कि समय क्लॉज द्वारा क्रम में है।मैं LastActivityAt, UserID, Category आईडी पर एक अनुक्रमणिका चाहता हूं। बस मस्ती के लिए, ऑर्डर हटाएं और देखें कि क्या यह 10 रिकॉर्ड्स जल्दी से लौटाता है। यदि ऐसा होता है, तो आप जानते हैं कि यह अन्य तालिकाओं में शामिल नहीं है। साथ ही * कॉलम के साथ * को प्रतिस्थापित करने में मददगार होगा क्योंकि सभी 3 टेबल कॉलम tempdb में हैं जैसे आप सॉर्ट कर रहे हैं - जैसा कि नील का उल्लेख है।

निष्पादन को देखते हुए योजना बना रही है आप अतिरिक्त तरह ध्यान देंगे - मुझे विश्वास है कि आदेश है जिसके द्वारा कुछ समय लेने के लिए जा रहा है। मुझे लगता है कि आपके पास 3 के साथ एक इंडेक्स था और यह 17 सेकंड था ... तो आप शामिल मानदंड (उपयोगकर्ता आईडी, श्रेणीआईडी) के लिए एक इंडेक्स और आखिरी प्रतिक्रियाशीलता के लिए एक और चाहते हैं - देखें कि यह बेहतर प्रदर्शन करता है या नहीं। इंडेक्स ट्यूनिंग विज़ार्ड के माध्यम से क्वेरी चलाने के लिए भी अच्छा होगा।

+0

मैं इसके साथ सहमत होगा। कोई सूचकांक होने पर आदेश लेना चाहिए। मैं निष्पादन योजनाओं में हालांकि नहीं देखता हूं। अजीब। – cdonner

+0

हाँ मैंने इसे ट्यूनिंग विज़ार्ड के माध्यम से चलाया और सिफारिशों को जोड़ने के बाद इसे 17 सेकंड तक घटा दिया। –

+0

हाँ द्वारा ऑर्डर को हटाकर इसे बहुत तेज़ बनाता है। मजेदार बात यह है कि अगर मैं इनर जॉइन में से किसी एक को हटा देता हूं और ऑर्डर को तेज़ी से छोड़ देता हूं। –

0

आप क्वेरी में से प्रत्येक को चलाने से पहले एसक्यूएल सर्वर कैश साफ़ कर है?

एसक्यूएल 2000 में, यह बी सी सी DROPCLEANBUFFERS की तरह कुछ है। अधिक जानकारी के लिए Google कमांड।

क्वेरी को देखते हुए, मैं

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

के लिए एक सूचकांक और संभवतः Stories.LastActivityAt

होता लेकिन हाँ , परिणाम की तरह लगता है कि कैशिंग के बोगस 'हो सकता है।

+0

@ रोबो: यह वास्तव में डीबीसीसी फ्रीप्रोकैच –

+0

कोई फायदा नहीं हुआ है। –

1

आप अपने हार्डवेयर सेटअप में डिस्क को अधिकतम कर रहे हैं।

अपने डेटा/लॉग/tempdb फ़ाइल स्थान के बारे में अपनी टिप्पणी को देखते हुए, मुझे लगता है कि ट्यूनिंग की किसी भी राशि एक bandaid होने जा रहा है।

250,000 पंक्तियां छोटी हैं। कल्पना करें कि आपकी समस्या 10 मिलियन पंक्तियों के साथ कितनी खराब होगी।

मैं सुझाव है कि आप अपने स्वयं के भौतिक ड्राइव (बेहतर एक RAID 0) पर tempdb ले जाएँ।

+0

हाँ मुझे लगता है कि आप सही हैं लेकिन यह सही नहीं लगता है कि मैं 250 से कम में समान डेटा प्राप्त करने के लिए 3 अलग-अलग प्रश्नों का उपयोग कर सकता हूं। लगता है कि क्वेरी प्लान खराब हो रहा है, आज रात कुछ एमएस लोगों को मारा जाएगा। –

+0

@ चाड मोरन: निराशावादी होने से नफरत है, लेकिन मुझे लगता है कि एमएस लोग आपको वही सलाह देंगे। जैसे ही किसी भी प्रश्न योजना को tempDB का उपयोग करने की आवश्यकता होती है, आप लगभग निश्चित रूप से एक बाधा होने जा रहे हैं –

1

ठीक है, इसलिए मेरी टेस्ट मशीन तेज़ नहीं है। असल में यह वास्तव में धीमा है। यह 1.6 गीगा, एन 1 जीबी रैम, कोई एकाधिक डिस्क नहीं, एसक्यूएल सर्वर, ओएस, और अतिरिक्त के लिए केवल एक एकल (धीमी गति से) डिस्क।

मैंने आपकी सारणी प्राथमिक और विदेशी कुंजी परिभाषित की है। 2 श्रेणियां, 500 यादृच्छिक उपयोगकर्ता, और 250000 यादृच्छिक कहानियां डालें।

उपरोक्त पहली क्वेरी को चलाने में 16 सेकंड लगते हैं (कोई योजना कैश नहीं है)। यदि मैं LastActivityAt कॉलम को अनुक्रमित करता हूं तो मुझे परिणाम एक सेकंड के भीतर मिलते हैं (यहां कोई योजना कैश नहीं है)।

यहां दी गई स्क्रिप्ट है जिसे मैं यह सब करने के लिए उपयोग करता था।

--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 @[email protected]+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 

यह निश्चित रूप से है जहां आपकी धीमी गति हो रही है। सॉर्टिंग मुख्य रूप से tempdb में किया जाता है और एक बड़ी तालिका एलओटीएस को जोड़ा जाएगा। इस कॉलम पर एक इंडेक्स होने से निश्चित रूप से ऑर्डर पर प्रदर्शन में सुधार होगा।

इसके अलावा, आपके प्राथमिक परिभाषित करने और विदेशी कुंजी एसक्यूएल सर्वर में मदद करता है immensly

आपका विधि है कि अपने कोड में सूचीबद्ध है सुरुचिपूर्ण, और मूल रूप से एक ही प्रतिक्रिया है कि cdonner सी # में और नहीं एसक्यूएल को छोड़कर लिखा है। डीबी ट्यूनिंग शायद बेहतर परिणाम भी देगा!

--Kris

0

आप कुछ समय के लिए SQL सर्वर के साथ काम किया है, तो आप यह है कि एक सवाल के सबसे छोटे परिवर्तन बेतहाशा अलग प्रतिक्रिया समय पैदा कर सकता है की खोज करेंगे। शुरुआती प्रश्न में मैंने जो पढ़ा है, और क्वेरी प्लान को देखते हुए, मुझे संदेह है कि ऑप्टिमाइज़र ने फैसला किया है कि आंशिक परिणाम बनाने का सबसे अच्छा तरीका है और फिर इसे एक अलग चरण के रूप में सॉर्ट करें। आंशिक परिणाम उपयोगकर्ता और कहानियों के तालिकाओं का एक संयुक्त है। यह tempdb में गठित किया गया है। इसलिए अत्यधिक डिस्क एक्सेस बनाने और फिर इस अस्थायी तालिका को सॉर्ट करने के कारण होता है।

मुझे लगता है कि समाधान कहानियों पर एक कंपाउंड इंडेक्स बनाना चाहिए। LastActivityAt, Stories.UserId, Stories.CategoryId। आदेश बहुत महत्वपूर्ण है, फ़ील्ड LastActivityAt पहले होना चाहिए।

संबंधित मुद्दे