क्वेरी पर प्रदर्शन ट्यूनिंग


9

इस क्वेरी प्रदर्शन को बेहतर बनाने के लिए मदद लेना।

एसक्यूएल सर्वर 2008 आर 2 एंटरप्राइज , मैक्स रैम 16 जीबी, सीपीयू 40, समानांतर डिग्री का मैक्स डिग्री 4।

SELECT DsJobStat.JobName AS JobName
    , AJF.ApplGroup AS GroupName
    , DsJobStat.JobStatus AS JobStatus
    , AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) AS ElapsedSecAVG
    , AVG(CAST(DsJobStat.CpuMSec AS FLOAT)) AS CpuMSecAVG 
FROM DsJobStat, AJF 
WHERE DsJobStat.NumericOrderNo=AJF.OrderNo 
AND DsJobStat.Odate=AJF.Odate 
AND DsJobStat.JobName NOT IN( SELECT [DsAvg].JobName FROM [DsAvg] )         
GROUP BY DsJobStat.JobName
, AJF.ApplGroup
, DsJobStat.JobStatus
HAVING AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) <> 0;

निष्पादन संदेश,

(0 row(s) affected)
Table 'AJF'. Scan count 11, logical reads 45, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'DsAvg'. Scan count 2, logical reads 1926, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'DsJobStat'. Scan count 1, logical reads 3831235, physical reads 85, read-ahead reads 3724396, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

SQL Server Execution Times:
      CPU time = 67268 ms,  elapsed time = 90206 ms.

टेबल्स की संरचना:

-- 212271023 rows
CREATE TABLE [dbo].[DsJobStat](
    [OrderID] [nvarchar](8) NOT NULL,
    [JobNo] [int] NOT NULL,
    [Odate] [datetime] NOT NULL,
    [TaskType] [nvarchar](255) NULL,
    [JobName] [nvarchar](255) NOT NULL,
    [StartTime] [datetime] NULL,
    [EndTime] [datetime] NULL,
    [NodeID] [nvarchar](255) NULL,
    [GroupName] [nvarchar](255) NULL,
    [CompStat] [int] NULL,
    [RerunCounter] [int] NOT NULL,
    [JobStatus] [nvarchar](255) NULL,
    [CpuMSec] [int] NULL,
    [ElapsedSec] [int] NULL,
    [StatusReason] [nvarchar](255) NULL,
    [NumericOrderNo] [int] NULL,
CONSTRAINT [PK_DsJobStat] PRIMARY KEY CLUSTERED 
(   [OrderID] ASC,
    [JobNo] ASC,
    [Odate] ASC,
    [JobName] ASC,
    [RerunCounter] ASC
));

-- 48992126 rows
CREATE TABLE [dbo].[AJF](  
    [JobName] [nvarchar](255) NOT NULL,
    [JobNo] [int] NOT NULL,
    [OrderNo] [int] NOT NULL,
    [Odate] [datetime] NOT NULL,
    [SchedTab] [nvarchar](255) NULL,
    [Application] [nvarchar](255) NULL,
    [ApplGroup] [nvarchar](255) NULL,
    [GroupName] [nvarchar](255) NULL,
    [NodeID] [nvarchar](255) NULL,
    [Memlib] [nvarchar](255) NULL,
    [Memname] [nvarchar](255) NULL,
    [CreationTime] [datetime] NULL,
CONSTRAINT [AJF$PrimaryKey] PRIMARY KEY CLUSTERED 
(   [JobName] ASC,
    [JobNo] ASC,
    [OrderNo] ASC,
    [Odate] ASC
));

-- 413176 rows
CREATE TABLE [dbo].[DsAvg](
    [JobName] [nvarchar](255) NULL,
    [GroupName] [nvarchar](255) NULL,
    [JobStatus] [nvarchar](255) NULL,
    [ElapsedSecAVG] [float] NULL,
    [CpuMSecAVG] [float] NULL
);

CREATE NONCLUSTERED INDEX [DJS_Dashboard_2] ON [dbo].[DsJobStat] 
(   [JobName] ASC,
    [Odate] ASC,
    [StartTime] ASC,
    [EndTime] ASC
)
INCLUDE ( [OrderID],
[JobNo],
[NodeID],
[GroupName],
[JobStatus],
[CpuMSec],
[ElapsedSec],
[NumericOrderNo]) ;

CREATE NONCLUSTERED INDEX [Idx_Dashboard_AJF] ON [dbo].[AJF] 
(   [OrderNo] ASC,
[Odate] ASC
)
INCLUDE ( [SchedTab],
[Application],
[ApplGroup]) ;

CREATE NONCLUSTERED INDEX [DsAvg$JobName] ON [dbo].[DsAvg] 
(   [JobName] ASC
)

निष्पादन योजना:

https://www.brentozar.com/pastetheplan/?id=rkUVhMlXM


जवाब मिलने के बाद अपडेट करें

आपका बहुत बहुत शुक्रिया @ जोए ओबिश

आप इस क्वेरी के मुद्दे के बारे में सही हैं जो DsJobStat और DsAvg के बीच है। यह कैसे शामिल नहीं है और नहीं में उपयोग करने के बारे में ज्यादा नहीं है।

जैसा कि आपने अनुमान लगाया है कि वास्तव में एक तालिका है।

CREATE TABLE [dbo].[DSJobNames](
    [JobName] [nvarchar](255) NOT NULL,
 CONSTRAINT [DSJobNames$PrimaryKey] PRIMARY KEY CLUSTERED 
(   [JobName] ASC
) ); 

मैंने आपके सुझाव की कोशिश की,

SELECT DsJobStat.JobName AS JobName
, AJF.ApplGroup AS GroupName
, DsJobStat.JobStatus AS JobStatus
, AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) AS ElapsedSecAVG
, Avg(CAST(DsJobStat.CpuMSec AS FLOAT)) AS CpuMSecAVG 
FROM DsJobStat
INNER JOIN DSJobNames jn
    ON jn.[JobName]= DsJobStat.[JobName]
INNER JOIN AJF 
    ON DsJobStat.Odate=AJF.Odate 
    AND DsJobStat.NumericOrderNo=AJF.OrderNo 
WHERE NOT EXISTS ( SELECT 1 FROM [DsAvg] WHERE jn.JobName =  [DsAvg].JobName )      
GROUP BY DsJobStat.JobName, AJF.ApplGroup, DsJobStat.JobStatus
HAVING AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) <> 0;   

निष्पादन संदेश:

(0 row(s) affected)
Table 'DSJobNames'. Scan count 5, logical reads 1244, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'DsAvg'. Scan count 5, logical reads 2129, physical reads 0, read-ahead reads 24, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'DsJobStat'. Scan count 8, logical reads 84, physical reads 0, read-ahead reads 83, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'AJF'. Scan count 5, logical reads 757999, physical reads 944, read-ahead reads 757311, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 21776 ms,  elapsed time = 33984 ms.

निष्पादन योजना: https://www.brentozar.com/pastetheplan/?id=rJVkLSZ7f


यदि यह विक्रेता कोड है जिसे आप बदल नहीं सकते हैं, तो सबसे अच्छी बात यह है कि विक्रेता के साथ एक समर्थन घटना खोलना, जितना संभव हो उतना दर्दनाक हो, और एक क्वेरी होने के लिए उन्हें हरा दें जिसके लिए कई को पूरा करने की आवश्यकता होती है। 413 हजार पंक्तियों वाली तालिका में मूल्यों को संदर्भित करने वाला NOT NOT खंड है, उह, उप-इष्टतम। DSJobStat पर सूचकांक स्कैन 212 मिलियन पंक्तियों को लौटा रहा है, जो 212 मिलियन नेस्टेड छोरों तक बुलबुले बनाता है, और आप देख सकते हैं कि 212 मिलियन रो काउंट्स लागत का 83% है। मुझे नहीं लगता है कि आप क्वेरी को फिर से लिखने या डेटा को शुद्ध किए बिना इसकी मदद कर सकते हैं ...
टोनी हिंकल

मुझे समझ में नहीं आया, इवान सुझाव कैसे आया, जिसने आपको पहली जगह में मदद नहीं की, दोनों जवाब स्पष्टीकरण के अलावा एक ही हैं। हालांकि, मैं यह नहीं देखता कि आपने पूरी तरह से इन दोनों लोगों ने आपको क्या सुझाव दिया है। इस प्रश्न को रोचक बनाया है।
कुमारहर्ष

जवाबों:


11

आइए जुड़ने के आदेश पर विचार करके शुरू करें। आपके पास क्वेरी में तीन तालिका संदर्भ हैं। किस ऑर्डर में शामिल होने से आपको सर्वश्रेष्ठ प्रदर्शन मिल सकता है? क्वेरी अनुकूलक सोचता है कि में शामिल होने से DsJobStatकरने के लिए DsAvgपंक्तियों की लगभग सभी (प्रमुखता अनुमान 1 पंक्ति के लिए 212,195,000 से गिर) समाप्त करेंगे। वास्तविक योजना हमें दिखाती है कि अनुमान वास्तविकता के बहुत करीब है (11 पंक्तियाँ शामिल होने से बच जाती हैं)। हालाँकि, जुड़ने को एक सही एंटी अर्ध मर्ज जॉइन के रूप में लागू किया जाता है, इसलिए DsJobStatतालिका से सभी 212 मिलियन पंक्तियों को केवल 11 पंक्तियों के उत्पादन के लिए स्कैन किया जाता है। यह निश्चित रूप से लंबी क्वेरी निष्पादन समय में योगदान दे सकता है, लेकिन मैं इसमें शामिल होने के लिए बेहतर भौतिक या तार्किक ऑपरेटर के बारे में नहीं सोच सकता जो बेहतर होता। मुझे यकीन है किDJS_Dashboard_2इंडेक्स का उपयोग अन्य प्रश्नों के लिए किया जाता है, लेकिन सभी अतिरिक्त कुंजी और शामिल कॉलम में इस क्वेरी के लिए अधिक IO की आवश्यकता होगी और आपको धीमा कर देगा। तो आप संभावित रूप से DsJobStatटेबल पर इंडेक्स स्कैन के साथ टेबल एक्सेस की समस्या रखते हैं ।

मैं मान रहा हूँ कि ज्वाइन AJFबहुत सेलेक्टिव नहीं है। यह वर्तमान में प्रदर्शन के उन मुद्दों के लिए प्रासंगिक नहीं है जिन्हें आप क्वेरी में देख रहे हैं, इसलिए मैं इस उत्तर के बाकी हिस्सों के लिए इसे अनदेखा करने जा रहा हूं। यदि तालिका में डेटा बदल जाता है तो यह बदल सकता है।

दूसरी समस्या जो योजना से स्पष्ट है वह है पंक्ति गणना स्पूल ऑपरेटर। यह बहुत हल्का ऑपरेटर है लेकिन यह 200 मिलियन से अधिक बार निष्पादित हो रहा है। ऑपरेटर वहाँ है क्योंकि क्वेरी के साथ लिखा गया है NOT IN। यदि एक एकल पूर्ण पंक्ति है DsAvgतो सभी पंक्तियों को समाप्त करना होगा। स्पूल उस चेक का कार्यान्वयन है। संभवत: वह तर्क नहीं है जो आप चाहते हैं, इसलिए आप उस भाग का उपयोग करने के साथ लिखना बेहतर होगा NOT EXISTS। उस पुनर्लेखन का वास्तविक लाभ आपके सिस्टम और डेटा पर निर्भर करेगा।

मैंने कुछ क्वेरी फिर से लिखने के लिए क्वेरी प्लान के आधार पर कुछ डेटा का मजाक उड़ाया। मेरी सारणी की परिभाषाएं आप से काफी भिन्न हैं क्योंकि यह हर एक स्तंभ के लिए डेटा को मॉक करने के लिए बहुत अधिक प्रयास होता। संक्षिप्त डेटा संरचनाओं के साथ भी मैं उस प्रदर्शन समस्या को पुन: उत्पन्न करने में सक्षम था जो आप अनुभव कर रहे हैं।

CREATE TABLE [dbo].[DsAvg](
    [JobName] [nvarchar](255) NULL
);

CREATE CLUSTERED INDEX CI_DsAvg ON [DsAvg] (JobName);

INSERT INTO [DsAvg] WITH (TABLOCK)
SELECT TOP (200000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);

CREATE TABLE [dbo].[DsJobStat](
    [JobName] [nvarchar](255) NOT NULL,
    [JobStatus] [nvarchar](255) NULL,
);

CREATE CLUSTERED INDEX CI_JobStat ON DsJobStat (JobName)

INSERT INTO [DsJobStat] WITH (TABLOCK)
SELECT [JobName], 'ACTIVE'
FROM [DsAvg] ds
CROSS JOIN (
SELECT TOP (1000) 1
FROM master..spt_values t1
) c (t);

INSERT INTO [DsJobStat] WITH (TABLOCK)
SELECT TOP (1000) '200001', 'ACTIVE'
FROM master..spt_values t1;

क्वेरी योजना के आधार पर, हम देख सकते हैं कि तालिका JobNameमें लगभग 200000 अद्वितीय मूल्य हैं DsAvg। उस तालिका में शामिल होने के बाद पंक्तियों की वास्तविक संख्या के आधार पर हम देख सकते हैं कि लगभग सभी JobNameमान तालिका DsJobStatमें भी हैं DsAvg। इस प्रकार, DsJobStatतालिका में JobNameस्तंभ के लिए 200001 अद्वितीय मान हैं और प्रति मान 1000 पंक्तियाँ हैं।

मेरा मानना ​​है कि यह क्वेरी प्रदर्शन समस्या का प्रतिनिधित्व करती है:

SELECT DsJobStat.JobName AS JobName, DsJobStat.JobStatus AS JobStatus
FROM DsJobStat
WHERE DsJobStat.JobName NOT IN( SELECT [DsAvg].JobName FROM [DsAvg] );

आपकी क्वेरी योजना ( GROUP BY,, HAVINGप्राचीन शैली में शामिल होने, आदि) के अन्य सभी सामान परिणाम पंक्तियों को 11 पंक्तियों में कम करने के बाद होता है। यह वर्तमान में एक क्वेरी प्रदर्शन बिंदु से कोई फर्क नहीं पड़ता है, लेकिन वहां अन्य चिंताएं हो सकती हैं जो आपके तालिकाओं में परिवर्तित डेटा से प्रकट हो सकती हैं।

मैं SQL Server 2017 में परीक्षण कर रहा हूं, लेकिन मुझे आपके समान मूल योजना आकार प्राप्त है:

योजना से पहले

मेरी मशीन पर, उस क्वेरी को निष्पादित करने के लिए 62219 एमएसपी समय का सीपीयू और 65576 एमएस का समय लगता है। यदि मैं उपयोग करने के लिए क्वेरी को फिर से लिखता हूं NOT EXISTS:

SELECT DsJobStat.JobName AS JobName, DsJobStat.JobStatus AS JobStatus
FROM DsJobStat
WHERE NOT EXISTS (SELECT 1 FROM [DsAvg] WHERE DsJobStat.JobName = [DsAvg].JobName);

कोई स्पूल नहीं

स्पूल को अब 212 मिलियन बार निष्पादित नहीं किया गया है और संभवतः इसका विक्रेता से इच्छित व्यवहार है। अब क्वेरी सीपीयू समय के 34516 एमएस और बीते हुए समय के 41132 एमएस में निष्पादित होती है। अधिकांश समय सूचकांक से 212 मिलियन पंक्तियों को स्कैन करने में खर्च किया जाता है।

उस क्वेरी के लिए यह सूचकांक स्कैन बहुत दुर्भाग्यपूर्ण है। औसत मूल्य पर हमारे पास 1000 पंक्तियाँ हैं JobName, लेकिन हम पहली पंक्ति को पढ़ने के बाद जानते हैं कि क्या हमें पहले वाली 1000 पंक्तियों की आवश्यकता होगी। हमें लगभग उन पंक्तियों की आवश्यकता नहीं है, लेकिन हमें अभी भी उन्हें स्कैन करने की आवश्यकता है। अगर हमें पता है कि पंक्तियाँ तालिका में बहुत सघन नहीं हैं और लगभग सभी को सम्मिलित रूप से समाप्त कर दिया जाएगा तो हम सूचकांक पर संभवतः अधिक कुशल IO पैटर्न की कल्पना कर सकते हैं। क्या होगा यदि SQL सर्वर प्रति अद्वितीय मूल्य प्रति पहली पंक्ति पढ़ता है JobName, अगर यह मान में है कि जाँच की गई थी DsAvg, और JobNameयदि वह था तो अगले मूल्य के आगे बस छोड़ दिया गया? इसके बजाय 212 मिलियन पंक्तियों को स्कैन करने के लिए एक सीक प्लान की आवश्यकता होती है जिसकी बजाय लगभग 200k निष्पादन हो सकता है।

यह ज्यादातर एक ऐसी तकनीक के साथ पुनरावृत्ति का उपयोग करके पूरा किया जा सकता है जो पॉल व्हाइट ने यहां वर्णित किया है । हम IO पैटर्न के लिए पुनरावृत्ति का उपयोग कर सकते हैं जो मैंने ऊपर वर्णित किया है:

WITH RecursiveCTE
AS
(
    -- Anchor
    SELECT TOP (1)
        [JobName]
    FROM dbo.DsJobStat AS T
    ORDER BY
        T.[JobName]

    UNION ALL

    -- Recursive
    SELECT R.[JobName]
    FROM
    (
        -- Number the rows
        SELECT 
            T.[JobName],
            rn = ROW_NUMBER() OVER (
                ORDER BY T.[JobName])
        FROM dbo.DsJobStat AS T
        JOIN RecursiveCTE AS R
            ON R.[JobName] < T.[JobName]
    ) AS R
    WHERE
        -- Only the row that sorts lowest
        R.rn = 1
)
SELECT js.*
FROM RecursiveCTE
INNER JOIN dbo.DsJobStat js ON RecursiveCTE.[JobName]= js.[JobName]
WHERE NOT EXISTS (SELECT 1 FROM [DsAvg] WHERE RecursiveCTE.JobName = [DsAvg].JobName)
OPTION (MAXRECURSION 0);

यह प्रश्न देखने के लिए बहुत कुछ है इसलिए मैं वास्तविक योजना की सावधानीपूर्वक जांच करने की सलाह देता हूं । सबसे पहले हम DsJobStatसभी अद्वितीय JobNameमूल्यों को प्राप्त करने के लिए 200002 इंडेक्स इंडेक्स के खिलाफ प्रयास करते हैं। फिर हम DsAvgसभी पंक्तियों को जोड़ते हैं और समाप्त करते हैं लेकिन एक। शेष पंक्ति के लिए, वापस जाएं DsJobStatऔर सभी आवश्यक कॉलम प्राप्त करें।

IO पैटर्न पूरी तरह से बदल जाता है। इससे पहले कि हमें यह मिले:

तालिका 'DsJobStat'। स्कैन काउंट 1, लॉजिकल रीड 1091651, फिजिकल रीड्स 13836, रीड-फॉरवर्ड रीड 181966

पुनरावर्ती क्वेरी के साथ हमें यह मिलता है:

तालिका 'DsJobStat'। स्कैन काउंट 200003, लॉजिकल 1398000, फिजिकल रीड 1, रीड-फॉरवर्ड रीड 7373 पढ़ता है

मेरी मशीन पर, नई क्वेरी सीपीयू समय के सिर्फ 6891 एमएस और बीते हुए समय के 7107 एमएस में निष्पादित होती है। ध्यान दें कि इस तरह से पुनरावृत्ति का उपयोग करने की आवश्यकता है कि डेटा मॉडल से कुछ गायब है (या शायद यह पोस्ट किए गए प्रश्न में केवल अस्थिर था)। यदि कोई अपेक्षाकृत छोटी तालिका है जिसमें सभी संभव हैं JobNamesतो उस तालिका का उपयोग करना बेहतर होगा जैसा कि बड़ी तालिका पर पुनरावृत्ति के विपरीत है। यह क्या उबलता है यदि आपके पास एक परिणाम सेट है जिसमें आपको सभी की JobNamesआवश्यकता होती है तो आप बाकी लापता कॉलमों को प्राप्त करने के लिए इंडेक्स सॉक्स का उपयोग कर सकते हैं। हालाँकि, आप ऐसा नहीं कर सकते हैं जिसके परिणामस्वरूप JobNamesआपको उस की आवश्यकता नहीं है।


मैंने सुझाव दिया NOT EXISTS। उन्होंने पहले से ही जवाब दिया "मैंने पहले ही दोनों की कोशिश की, जुड़ें और मौजूद नहीं हैं, इससे पहले कि मैं सवाल पोस्ट करूं। बहुत अंतर नहीं।"
इवान कैरोल

1
मुझे यह जानने के लिए उत्सुक होना चाहिए कि क्या पुनरावर्ती विचार काम करता है, हालांकि यह भयानक है।
इवान कैरोल

मुझे लगता है कि क्लॉज होने की आवश्यकता नहीं है। "ElapsedSec null नहीं है" जहां क्लॉज करेगा। साथ ही मुझे लगता है कि पुनरावर्ती CTE की आवश्यकता नहीं है। आप पंक्ति_number () का उपयोग कर सकते हैं (नाम से पदनाम आदेश द्वारा) rn मौजूद नहीं है (चयन करें) क्वेरी)। आपको मेरे विचार के बारे में क्या कहना है?
कुमारहर्ष

@ जोए ओबिश, मैंने अपनी पोस्ट अपडेट की। बहुत बहुत धन्यवाद।
वेंडी

हाँ, पुनरावर्ती CTE 1 मिनट तक रो_नंबर () नाम से कार्य विभाजन (नाम के अनुसार विभाजन) करते हैं। लेकिन एक ही समय में मुझे आपके नमूना डेटा का उपयोग करके पुनरावर्ती CTE में कोई अतिरिक्त लाभ नहीं दिखाई दिया।
कुमारहर्ष

0

देखें कि क्या होता है यदि आप स्थिति को फिर से लिखते हैं,

AND DsJobStat.JobName NOT IN( SELECT [DsAvg].JobName FROM [DsAvg] )         

सेवा

AND NOT EXISTS ( SELECT 1 FROM [DsAvg] AS d WHERE d.JobName = DsJobStat.JobName )

अपने SQL89 में फिर से लिखने पर विचार करें क्योंकि यह शैली डरावनी है।

के बजाय

FROM DsJobStat, AJF 
WHERE DsJobStat.NumericOrderNo=AJF.OrderNo 
AND DsJobStat.Odate=AJF.Odate 

प्रयत्न

FROM DsJobStat
INNER JOIN AJF ON (
  DsJobStat.NumericOrderNo=AJF.OrderNo 
  AND DsJobStat.Odate=AJF.Odate
)

मुझे यह भी संदेह है कि इस स्थिति को बेहतर लिखा जा सकता है लेकिन हमें यह जानना होगा कि क्या हो रहा है

HAVING AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) <> 0;

क्या आपको वास्तव में जानना है कि औसत शून्य नहीं है, या सिर्फ समूह का एक तत्व शून्य नहीं है?


@EvanCarroll। इससे पहले कि मैंने प्रश्न पोस्ट किया, मैंने पहले से ही दोनों को शामिल किया और शामिल नहीं किया। ज्यादा अंतर नहीं।
वेंडी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.