اشتراکها
اشتراکها
اینفوگرافی کیفیت کد ها در سال 2016
اشتراکها
آفیس 2016 برای مک
پس از آشنایی با «روش کار با فایلهای ایستا در برنامههای React»، اکنون اگر این فایلها ایستا نباشند و توسط یک برنامهی ASP.NET Core بازگشت داده شوند، چطور میتوان از آنها در برنامههای React استفاده کرد؟
برپایی پروژههای مورد نیاز
ابتدا یک پوشهی جدید را مانند DownloadFilesSample، ایجاد کرده و در داخل آن دستور زیر را اجرا میکنیم:
در مورد این قالب که امکان تجربهی توسعهی یکپارچهی ASP.NET Core و React را میسر میکند، در مطلب «روش یکی کردن پروژههای React و ASP.NET Core» بیشتر بحث کردیم.
سپس در این پوشه، پوشهی ClientApp پیشفرض آنرا حذف میکنیم؛ چون کمی قدیمی است. همچنین فایلهای کنترلر و سرویس آب و هوای پیشفرض آنرا به همراه پوشهی صفحات Razor آن، حذف میکنیم.
به علاوه بجای تنظیم پیش فرض زیر در فایل کلاس آغازین برنامه:
از تنظیم زیر استفاده کردهایم تا با هر بار تغییری در کدهای پروژهی ASP.NET، یکبار دیگر از صفر npm start اجرا نشود:
بدیهی است در این حالت باید از طریق خط فرمان به پوشهی clientApp وارد شد و دستور npm start را یکبار به صورت دستی اجرا کرد، تا این وب سرور بر روی پورت 3000، راه اندازی شود.
اکنون در ریشهی پروژهی ASP.NET Core ایجاد شده، دستور زیر را صادر میکنیم تا پروژهی کلاینت React را با فرمت جدید آن ایجاد کند:
سپس وارد این پوشهی جدید شده و بستههای زیر را نصب میکنیم:
توضیحات:
- برای استفاده از شیوهنامههای بوت استرپ، بستهی bootstrap نیز در اینجا نصب میشود که برای افزودن فایل bootstrap.css آن به پروژهی React خود، ابتدای فایل clientapp\src\index.js را به نحو زیر ویرایش خواهیم کرد:
این import به صورت خودکار توسط webpack ای که در پشت صحنه کار bundling & minification برنامه را انجام میدهد، مورد استفاده قرار میگیرد.
- برای دریافت فایلها از سمت سرور، از کتابخانهی معروف axios استفاده خواهیم کرد.
کدهای سمت سرور دریافت فایلهای پویا
در اینجا کدهای سمت سرور برنامه، یک فایل PDF ساده را بازگشت میدهند. این محتوای باینری میتواند حاصل اجرای یک گزارش اکسل، PDF و یا کلا هر نوع فایلی باشد:
فایل بازگشتی فوق که در این مثال در مسیر wwwroot\app_data\sample.pdf برنامهی وب کپی شدهاست، در نهایت با آدرس api/Reports/GetPdfReport در سمت کلاینت قابل دسترسی خواهد بود.
روش دریافت محتوای باینری در برنامههای React
برای دریافت یک محتوای باینری از سرور توسط axios مانند تصاویر، فایلهای PDF و اکسل و غیره، مهمترین نکته، تنظیم ویژگی responseType آن به blob است:
ساخت URL برای دسترسی به اطلاعات باینری
تمام مرورگرهای جدید از ایجاد URL برای اشیاء Blob دریافتی از سمت سرور، توسط متد توکار URL.createObjectURL پشتیبانی میکنند. این متد، شیء URL را از شیء window جاری دریافت میکند و سپس اطلاعات باینری را دریافت کرده و آدرسی را جهت دسترسی موقت به آن تولید میکند. حاصل آن، یک URL ویژهاست مانند blob:https://localhost:5001/03edcadf-89fd-48b9-8a4a-e9acf09afd67 که گشودن آن در مرورگر، یا سبب نمایش آن تصویر و یا دریافت مستقیم فایل خواهد شد.
در ادامه کدهای تبدیل blob دریافت شدهی از سرور را به این URL ویژه، مشاهده میکنید:
توضیحات:
- توسط useEffect Hook و بدون ذکر وابستگی خاصی در آن، سبب شبیه سازی رویداد componentDidUpdate شدهایم. به این معنا که متد getResults فراخوانی شدهی در آن، پس از رندر کامپوننت در DOM فراخوانی میشود و بهترین محلی است که از آن میتوان برای ارسال درخواستهای Ajaxای به سمت سرور و دریافت اطلاعات از backend، استفاده کرد و سپس setState را با اطلاعات جدید فراخوانی نمود. معادل setState در اینجا نیز، همان شیء حالتی است که توسط useState Hook و متد setBlobInfo آن تعریف کردهایم.
- پس از دریافت headers و data از سرور، با استفاده از متد createObjectURL، آنرا تبدیل به یک blob URL کردهایم.
- همچنین در سمت سرور، پارامتر fileDownloadName را نیز تنظیم کردهایم. این نام در سمت کلاینت، توسط هدری با کلید content-disposition ظاهر میشود:
بنابراین میتوان آنرا تجزیه کرد و سپس filename را از آن استخراج نمود.
- اکنون که نام فایل و URL دسترسی به دادهی فایل باینری دریافتی از سرور را استخراج و ایجاد کردهایم. با فراخوانی متد setBlobInfo، سبب تنظیم متغیر حالت blobInfo خواهیم شد. این مورد، رندر مجدد UI را سبب شده و توسط آن میتوان برای مثال فایل PDF دریافتی را نمایش داد.
نمایش فایل PDF دریافتی از سرور، به همراه دکمههای دریافت، چاپ و بازکردن آن در برگهای جدید
در ادامه کدهای کامل قسمت رندر این کامپوننت را مشاهده میکنید:
که چنین خروجی را ایجاد میکند:
در اینجا با انتساب مستقیم blob URL ایجاد شده، به خواص src و یا data اشیائی مانند iframe ،object و یا embed، میتوان سبب نمایش فایل pdf دریافتی از سرور شد. این نمایش نیز توسط قابلیتهای توکار مرورگر صورت میگیرد و نیاز به نصب افزونهی خاصی را ندارد.
در ادامه کدهای مرتبط با سه دکمهی چاپ، دریافت و بازکردن فایل دریافتی از سرور را مشاهده میکنید.
مدیریت دکمهی چاپ PDF
پس از اینکه به blobUrl دسترسی یافتیم، اکنون میتوان یک iframe مخفی را ایجاد کرد، سپس src آنرا به این آدرس ویژه تنظیم نمود و در آخر متد print آنرا فراخوانی کرد که سبب نمایش خودکار دیالوگ چاپ مرورگر میشود:
مدیریت دکمهی نمایش فایل PDF در یک برگهی جدید
اگر علاقمند بودید تا این فایل PDF را به صورت تمام صفحه و در برگهای جدید نمایش دهید، میتوان از متد window.open استفاده کرد:
مدیریت دکمهی دریافت فایل PDF
بجای نمایش فایل PDF میتوان دکمهای را بر روی صفحه قرار داد که با کلیک بر روی آن، این فایل توسط مرورگر به صورت متداولی جهت دریافت به کاربر ارائه شود:
در اینجا یک anchor جدید به صورت مخفی به صفحه اضافه میشود که href آن به blobUrl تنظیم شدهاست و همچنین از فایل fileName استخراجی نیز در اینجا جهت ارائهی نام اصلی فایل دریافتی از سرور، کمک گرفته شدهاست. سپس متد click آن فراخوانی خواهد شد. این روش در مورد تدارک دکمهی دریافت تمام blobهای دریافتی از سرور کاربرد دارد و منحصر به فایلهای PDF نیست.
اگر خواستید عملیات axios.get و دریافت فایل، با هم یکی شوند، میتوان متد handleDownloadPdf را پس از پایان کار await axios.get، فراخوانی کرد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: DownloadFilesSample.zip
برای اجرای آن، پس از صدور فرمان dotnet restore که سبب بازیابی وابستگیهای سمت کلاینت نیز میشود، ابتدا به پوشهی clientapp مراجعه کرده و فایل run.cmd را اجرا کنید. با اینکار react development server بر روی پورت 3000 شروع به کار میکند. سپس به پوشهی اصلی برنامهی ASP.NET Core بازگشته و فایل dotnet_run.bat را اجرا کنید. این اجرا سبب راه اندازی وب سرور برنامه و همچنین ارائهی برنامهی React بر روی پورت 5001 میشود.
برپایی پروژههای مورد نیاز
ابتدا یک پوشهی جدید را مانند DownloadFilesSample، ایجاد کرده و در داخل آن دستور زیر را اجرا میکنیم:
> dotnet new react
سپس در این پوشه، پوشهی ClientApp پیشفرض آنرا حذف میکنیم؛ چون کمی قدیمی است. همچنین فایلهای کنترلر و سرویس آب و هوای پیشفرض آنرا به همراه پوشهی صفحات Razor آن، حذف میکنیم.
به علاوه بجای تنظیم پیش فرض زیر در فایل کلاس آغازین برنامه:
spa.UseReactDevelopmentServer(npmScript: "start");
spa.UseProxyToSpaDevelopmentServer("http://localhost:3000");
اکنون در ریشهی پروژهی ASP.NET Core ایجاد شده، دستور زیر را صادر میکنیم تا پروژهی کلاینت React را با فرمت جدید آن ایجاد کند:
> create-react-app clientapp
> cd clientapp > npm install --save bootstrap axios
- برای استفاده از شیوهنامههای بوت استرپ، بستهی bootstrap نیز در اینجا نصب میشود که برای افزودن فایل bootstrap.css آن به پروژهی React خود، ابتدای فایل clientapp\src\index.js را به نحو زیر ویرایش خواهیم کرد:
import "bootstrap/dist/css/bootstrap.css";
- برای دریافت فایلها از سمت سرور، از کتابخانهی معروف axios استفاده خواهیم کرد.
کدهای سمت سرور دریافت فایلهای پویا
در اینجا کدهای سمت سرور برنامه، یک فایل PDF ساده را بازگشت میدهند. این محتوای باینری میتواند حاصل اجرای یک گزارش اکسل، PDF و یا کلا هر نوع فایلی باشد:
using Microsoft.AspNetCore.Mvc; namespace DownloadFilesSample.Controllers { [Route("api/[controller]")] public class ReportsController : Controller { [HttpGet("[action]")] public IActionResult GetPdfReport() { return File(virtualPath: "~/app_data/sample.pdf", contentType: "application/pdf", fileDownloadName: "sample.pdf"); } } }
روش دریافت محتوای باینری در برنامههای React
برای دریافت یک محتوای باینری از سرور توسط axios مانند تصاویر، فایلهای PDF و اکسل و غیره، مهمترین نکته، تنظیم ویژگی responseType آن به blob است:
const getResults = async () => { const { headers, data } = await axios.get(apiUrl, { responseType: "blob" }); }
ساخت URL برای دسترسی به اطلاعات باینری
تمام مرورگرهای جدید از ایجاد URL برای اشیاء Blob دریافتی از سمت سرور، توسط متد توکار URL.createObjectURL پشتیبانی میکنند. این متد، شیء URL را از شیء window جاری دریافت میکند و سپس اطلاعات باینری را دریافت کرده و آدرسی را جهت دسترسی موقت به آن تولید میکند. حاصل آن، یک URL ویژهاست مانند blob:https://localhost:5001/03edcadf-89fd-48b9-8a4a-e9acf09afd67 که گشودن آن در مرورگر، یا سبب نمایش آن تصویر و یا دریافت مستقیم فایل خواهد شد.
در ادامه کدهای تبدیل blob دریافت شدهی از سرور را به این URL ویژه، مشاهده میکنید:
import axios from "axios"; import React, { useEffect, useState } from "react"; export default function DisplayPdf() { const apiUrl = "https://localhost:5001/api/Reports/GetPdfReport"; const [blobInfo, setBlobInfo] = useState({ blobUrl: "", fileName: "" }); useEffect(() => { getResults(); }, []); const getResults = async () => { try { const { headers, data } = await axios.get(apiUrl, { responseType: "blob" }); console.log("headers", headers); const pdfBlobUrl = window.URL.createObjectURL(data); console.log("pdfBlobUrl", pdfBlobUrl); const fileName = headers["content-disposition"] .split(";") .find(n => n.includes("filename=")) .replace("filename=", "") .trim(); console.log("filename", fileName); setBlobInfo({ blobUrl: pdfBlobUrl, fileName: fileName }); } catch (error) { console.log(error); } };
- توسط useEffect Hook و بدون ذکر وابستگی خاصی در آن، سبب شبیه سازی رویداد componentDidUpdate شدهایم. به این معنا که متد getResults فراخوانی شدهی در آن، پس از رندر کامپوننت در DOM فراخوانی میشود و بهترین محلی است که از آن میتوان برای ارسال درخواستهای Ajaxای به سمت سرور و دریافت اطلاعات از backend، استفاده کرد و سپس setState را با اطلاعات جدید فراخوانی نمود. معادل setState در اینجا نیز، همان شیء حالتی است که توسط useState Hook و متد setBlobInfo آن تعریف کردهایم.
- پس از دریافت headers و data از سرور، با استفاده از متد createObjectURL، آنرا تبدیل به یک blob URL کردهایم.
- همچنین در سمت سرور، پارامتر fileDownloadName را نیز تنظیم کردهایم. این نام در سمت کلاینت، توسط هدری با کلید content-disposition ظاهر میشود:
ontent-disposition: "attachment; filename=sample.pdf; filename*=UTF-8''sample.pdf"
- اکنون که نام فایل و URL دسترسی به دادهی فایل باینری دریافتی از سرور را استخراج و ایجاد کردهایم. با فراخوانی متد setBlobInfo، سبب تنظیم متغیر حالت blobInfo خواهیم شد. این مورد، رندر مجدد UI را سبب شده و توسط آن میتوان برای مثال فایل PDF دریافتی را نمایش داد.
نمایش فایل PDF دریافتی از سرور، به همراه دکمههای دریافت، چاپ و بازکردن آن در برگهای جدید
در ادامه کدهای کامل قسمت رندر این کامپوننت را مشاهده میکنید:
import axios from "axios"; import React, { useEffect, useState } from "react"; export default function DisplayPdf() { // ... const { blobUrl } = blobInfo; return ( <> <h1>Display PDF Files</h1> <button className="btn btn-info" onClick={handlePrintPdf}> Print PDF </button> <button className="btn btn-primary ml-2" onClick={handleShowPdfInNewTab}> Show PDF in a new tab </button> <button className="btn btn-success ml-2" onClick={handleDownloadPdf}> Download PDF </button> <section className="card mb-5 mt-3"> <div className="card-header"> <h4>using iframe</h4> </div> <div className="card-body"> <iframe title="PDF Report" width="100%" height="600" src={blobUrl} type="application/pdf" ></iframe> </div> </section> <section className="card mb-5"> <div className="card-header"> <h4>using object</h4> </div> <div className="card-body"> <object data={blobUrl} aria-label="PDF Report" type="application/pdf" width="100%" height="100%" ></object> </div> </section> <section className="card mb-5"> <div className="card-header"> <h4>using embed</h4> </div> <div className="card-body"> <embed aria-label="PDF Report" src={blobUrl} type="application/pdf" width="100%" height="100%" ></embed> </div> </section> </> ); }
در اینجا با انتساب مستقیم blob URL ایجاد شده، به خواص src و یا data اشیائی مانند iframe ،object و یا embed، میتوان سبب نمایش فایل pdf دریافتی از سرور شد. این نمایش نیز توسط قابلیتهای توکار مرورگر صورت میگیرد و نیاز به نصب افزونهی خاصی را ندارد.
در ادامه کدهای مرتبط با سه دکمهی چاپ، دریافت و بازکردن فایل دریافتی از سرور را مشاهده میکنید.
مدیریت دکمهی چاپ PDF
پس از اینکه به blobUrl دسترسی یافتیم، اکنون میتوان یک iframe مخفی را ایجاد کرد، سپس src آنرا به این آدرس ویژه تنظیم نمود و در آخر متد print آنرا فراخوانی کرد که سبب نمایش خودکار دیالوگ چاپ مرورگر میشود:
const handlePrintPdf = () => { const { blobUrl } = blobInfo; if (!blobUrl) { throw new Error("pdfBlobUrl is null"); } const iframe = document.createElement("iframe"); iframe.style.display = "none"; iframe.src = blobUrl; document.body.appendChild(iframe); if (iframe.contentWindow) { iframe.contentWindow.print(); } };
مدیریت دکمهی نمایش فایل PDF در یک برگهی جدید
اگر علاقمند بودید تا این فایل PDF را به صورت تمام صفحه و در برگهای جدید نمایش دهید، میتوان از متد window.open استفاده کرد:
const handleShowPdfInNewTab = () => { const { blobUrl } = blobInfo; if (!blobUrl) { throw new Error("pdfBlobUrl is null"); } window.open(blobUrl); };
مدیریت دکمهی دریافت فایل PDF
بجای نمایش فایل PDF میتوان دکمهای را بر روی صفحه قرار داد که با کلیک بر روی آن، این فایل توسط مرورگر به صورت متداولی جهت دریافت به کاربر ارائه شود:
const handleDownloadPdf = () => { const { blobUrl, fileName } = blobInfo; if (!blobUrl) { throw new Error("pdfBlobUrl is null"); } const anchor = document.createElement("a"); anchor.style.display = "none"; anchor.href = blobUrl; anchor.download = fileName; document.body.appendChild(anchor); anchor.click(); };
اگر خواستید عملیات axios.get و دریافت فایل، با هم یکی شوند، میتوان متد handleDownloadPdf را پس از پایان کار await axios.get، فراخوانی کرد.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: DownloadFilesSample.zip
برای اجرای آن، پس از صدور فرمان dotnet restore که سبب بازیابی وابستگیهای سمت کلاینت نیز میشود، ابتدا به پوشهی clientapp مراجعه کرده و فایل run.cmd را اجرا کنید. با اینکار react development server بر روی پورت 3000 شروع به کار میکند. سپس به پوشهی اصلی برنامهی ASP.NET Core بازگشته و فایل dotnet_run.bat را اجرا کنید. این اجرا سبب راه اندازی وب سرور برنامه و همچنین ارائهی برنامهی React بر روی پورت 5001 میشود.
سلام،
من SQL Server 2012 ندارم، ولی تا اونجایی که متوجه شدم بر اساس شواهد دو کوئری زیر باید یک نتیجه رو برگردانند. منظورم اینکه که با first_value میشه last_value هم شبیه سازی کرد، فقط کافیه که در ماده order by از کلید واژه DESC استفاده بشه. اگه من اشتباه میکنم لطفا راهنمایی بفرمایید.
من SQL Server 2012 ندارم، ولی تا اونجایی که متوجه شدم بر اساس شواهد دو کوئری زیر باید یک نتیجه رو برگردانند. منظورم اینکه که با first_value میشه last_value هم شبیه سازی کرد، فقط کافیه که در ماده order by از کلید واژه DESC استفاده بشه. اگه من اشتباه میکنم لطفا راهنمایی بفرمایید.
SELECT s.SalesOrderID,s.SalesOrderDetailID,s.OrderQty, LAST_VALUE(SalesOrderDetailID) OVER (PARTITION BY SalesOrderID ORDER BY SalesOrderDetailID) LstValue FROM Test_First_Last_Value s WHERE SalesOrderID IN (43670, 43669, 43667, 43663) ORDER BY s.SalesOrderID,s.SalesOrderDetailID,s.OrderQty SELECT s.SalesOrderID,s.SalesOrderDetailID,s.OrderQty, FITST_VALUE(SalesOrderDetailID) OVER (PARTITION BY SalesOrderID ORDER BY SalesOrderDetailID DESC) FstValue FROM Test_First_Last_Value s WHERE SalesOrderID IN (43670, 43669, 43667, 43663) ORDER BY s.SalesOrderID,s.SalesOrderDetailID,s.OrderQty
مطلب "منسوخ شدهها در نگارشهای جدید SQL server" را احتمالا به خاطر دارید. جهت تکمیل آن، کوئری زیر را هم میتوان ذکر کرد:
SELECT instance_name,
cntr_value
FROM sys.dm_os_performance_counters
WHERE OBJECT_NAME = 'SQLServer:Deprecated Features'
AND cntr_value > 0
ORDER BY
cntr_value DESC
توسط این کوئری گزارشی از منسوخ شدههای مورد استفاده در دیتابیسهای شما ارائه میشود. برای مثال چندبار از text و ntext استفاده کردهاید، آیا هنوز compatibility level دیتابیسهای خود را تغییر ندادهاید و مثالهایی از این دست.
برای مثال جهت یافتن سریع فیلدهای منسوخ شده text و image دیتابیس جاری از کوئری زیر میتوان کمک گرفت:
SELECT O.Name,
col.name AS ColName,
systypes.name
FROM syscolumns col
INNER JOIN sysobjects O
ON col.id = O.id
INNER JOIN systypes
ON col.xtype = systypes.xtype
WHERE O.Type = 'U'
AND OBJECTPROPERTY(o.ID, N'IsMSShipped') = 0
AND systypes.name IN ('text', 'ntext', 'image')
ORDER BY
O.Name,
Col.Name
مطالب
بررسی کارآیی کوئریها در SQL Server - قسمت ششم - بررسی عملگرهای دسترسی به دادهها در یک Query Plan
پس از آشنایی مقدماتی با نحوهی خواندن یک Query Plan، اکنون نوبت به بررسی عملگرهایی است که در آن مشاهده میشوند و همچنین تغییرات در کوئریها چگونه بر روی آنها تاثیر گذاشته و آنها را تغییر میدهند و این تغییرات چه تاثیری را بر روی کارآیی خواهند داشت.
عملگرهای Scans و Seeks
در حالت کلی میتوان دو نوع جدول بدون و با ایندکس را درنظر گرفت. در حالت جداول بدون ایندکس، برای جستجوی اطلاعات نیاز به Table Scan وجود دارد و برعکس آن شامل یک Clustered index scan خواهد بود. گاهی از اوقات Clustered index scanها بهترین روش دریافت اطلاعات هستند و گاهی از اوقات خیر و نیاز به بررسی بیشتری دارند. بنابراین قانون کلی، حذف آنها به محض مشاهده، نیست.
نوع دیگر عملگرهای دسترسی به دادهها، Seeks هستند که شامل Clustered index seeks و Non-clustered index seeks میشوند. در بسیاری از موارد عنوان میشود که Seeks کارآیی بهتری را به همراه دارند. هرچند این مورد نیاز به بررسی بیشتری دارد که در ادامه با مثالهایی آنها را مرور خواهیم کرد.
بررسی عملگر Table scan در یک Query Plan
در ادامه تعدادی از عملگرهای مرتبط با data access را از لحاظ نحوهی انتخاب و تغییر آنها توسط بهینه ساز کوئریهای SQL Server بررسی میکنیم. برای این منظور ابتدا در management studio از منوی Query، گزینهی Include actual execution plan را انتخاب میکنیم. سپس کوئریهای زیر را اجرا میکنیم:
در اینجا در ابتدا، تمام رکوردهای جدول [Sales].[Orders]، به جدول [Sales].[Copy_Orders] کپی میشوند. سپس یک کوئری را بر روی این جدول کپی، اجرا کردهایم.
همانطور که مشاهده میکنید، برای برآورده کردن قسمت where این کوئری، یک Table Scan صورت گرفتهاست؛ چون این جدول کپی، به همراه هیچ ایندکسی نیست. به همین جهت برای یافتن رکوردهای مدنظر، راه دیگری بجز اسکن کل جدول بانک اطلاعاتی وجود ندارد که بسیار ناکارآمد است.
همچنین اگر به برگهی messages دقت کنیم، با توجه به روشن بودن STATISTICS IO، میزان logical reads نیز قابل مشاهدهاست:
به علاوه اجرای آن نیز کمی بیشتر از نیم ثانیه، طول کشیدهاست:
بررسی عملگر Index Seek در یک Query Plan
اکنون سؤال اینجا است که آیا میتوان این وضعیت را بهبود بخشید؟
بله. برای این منظور یک NONCLUSTERED INDEX را بر روی جدول کپی، ایجاد میکنیم؛ به نحوی که CustomerID لحاظ شدهی در قسمت where کوئری را پوشش دهد:
چون مطابق کوئری، [OrderID] و [OrderDate] در قسمت where ذکر نشدهاند، در اینجا INCLUDE شدهاند.
در ادامه مجددا همان کوئری را اجرا میکنیم:
که سبب تولید کوئری پلن زیر میشود:
اینبار عملگر Table Scan قبلی به یک عملگر Index Seek بر روی NONCLUSTERED INDEX تعریف شده، تغییر کردهاست و اگر به آمار I/O آن دقت کنیم، logical reads 106 قابل مشاهدهاست که بهبود قابل ملاحظهای است نسبت به عدد 689 قبلی.
بررسی عملگر Clustered index scan در یک Query Plan
در ادامه همین کوئری را بر روی جدول [Sales].[Orders] اصلی اجرا میکنیم:
که به صورت پیشفرض شامل این ایندکسها است:
اجرای کوئری فوق، چنین کوئری پلنی را تولید میکند:
جدول [Sales].[Orders]، یک CLUSTERED INDEX را بر روی [OrderID] دارد و یک NONCLUSTERED INDEX را بر روی [CustomerID].
در کوئری پلن تولید شده، یک Clustered index scan مشاهده میشود. علت اینجا است که هرچند در جدول [Sales].[Orders] یک NONCLUSTERED INDEX بر روی [CustomerID] تعریف شدهاست:
اما قسمت INCLUDE ایندکس قبلی را که تعریف کردیم، ندارد و به همراه [CustomerID] و [OrderDate] نیست. به همین جهت اینبار logical reads 692 است.
بنابراین وجود عملگر Clustered index scan در یک کوئری پلن، یعنی نیاز به خواندن و اسکن کل جدول وجود دارد. برای اثبات آن، همین کوئری قبلی را که بر روی [Sales].[Orders] انجام دادیم، اینبار بدون قسمت where آن اجرا کنید. یعنی کوئری بر روی کل جدول انجام شود:
سپس به برگهی messages مراجعه کرده و عدد logical reads آنرا مشاهده کنید. این عدد دقیقا با عدد logical reads کوئری where دار، یکی است؛ که بیانگر اسکن کامل جدول در حالت Clustered index scan است.
سؤال: آیا Clustered index scan همواره کل یک جدول را اسکن میکند؟
پاسخ: خیر. اگر یک کوئری برای مثال دارای top/min/max باشد، کل جدول اسکن نخواهد شد:
تفاوت این کوئری با کوئریهای قبلی، در داشتن یک top 10 است. اگر آنرا اجرا کنیم، به کوئری پلن زیر خواهیم رسید:
هرچند در اینجا هم یک Clustered index scan صورت گرفته، اما اگر به برگهی messages آن مراجعه کنیم، آمار I/O آن بیانگر تنها logical reads 5 است که معادل اسکن کل جدول نیست:
مقایسهی عملگرهای Index Scan و Index Seek
ابتدا کوئری زیر را اجرا میکنیم:
این کوئری با کوئری قبلی از لحاظ قسمت select اندکی متفاوت بوده و در آن OrderDate حذف شدهاست. در قسمت where نیز کوئری بر روی OrderID صورت گرفتهاست.
در این جدول ایندکسی بر روی CustomerID وجود دارد و همچنین کلید اصلی جدول، OrderID است.
پس از اجرای این کوئری، به کوئری پلن زیر خواهیم رسید:
که بیانگر یک Index Scan است و نکتهی جالب آن، استفادهی از ایندکس FK_Sales_Orders_CustomerID میباشد (نام این شیء، ذیل آیکن عملگر، مشخص است). یعنی SQL Server در اینجا از یک non-clustered index تعریف شدهی بر روی CustomerID استفاده کردهاست.
اکنون اگر OrderID را تغییر دهیم چه اتفاقی رخ میدهد؟
اینبار به یک clustered index seek رسیدیم که بر روی کلید اصلی جدول یا همان PK_Sales_Orders که ذیل عملگر مشخص شده، رخ دادهاست:
در این مثال با دو ورودی مختلف، دو کوئری پلن مختلف تولید شدهاست؛ که مرتبط است با میزان اطلاعاتی که قرار است بازگشت داده شود.
اگر این دو کوئری را با هم اجرا کنیم (در طی یک batch)، به پلن مقایسهای زیر خواهیم رسید که در آن هزینهی Index Scan بیشتر است از clustered index seek:
به همراه آمار CPU و I/O ای به صورت زیر که اولی مرتبط است با index scan و دومی با clustered index seek:
به همین جهت است که عنوان میشود، scanها خوب نیستند و seekها بهترند.
عملگرهای Scans و Seeks
در حالت کلی میتوان دو نوع جدول بدون و با ایندکس را درنظر گرفت. در حالت جداول بدون ایندکس، برای جستجوی اطلاعات نیاز به Table Scan وجود دارد و برعکس آن شامل یک Clustered index scan خواهد بود. گاهی از اوقات Clustered index scanها بهترین روش دریافت اطلاعات هستند و گاهی از اوقات خیر و نیاز به بررسی بیشتری دارند. بنابراین قانون کلی، حذف آنها به محض مشاهده، نیست.
نوع دیگر عملگرهای دسترسی به دادهها، Seeks هستند که شامل Clustered index seeks و Non-clustered index seeks میشوند. در بسیاری از موارد عنوان میشود که Seeks کارآیی بهتری را به همراه دارند. هرچند این مورد نیاز به بررسی بیشتری دارد که در ادامه با مثالهایی آنها را مرور خواهیم کرد.
بررسی عملگر Table scan در یک Query Plan
در ادامه تعدادی از عملگرهای مرتبط با data access را از لحاظ نحوهی انتخاب و تغییر آنها توسط بهینه ساز کوئریهای SQL Server بررسی میکنیم. برای این منظور ابتدا در management studio از منوی Query، گزینهی Include actual execution plan را انتخاب میکنیم. سپس کوئریهای زیر را اجرا میکنیم:
SET STATISTICS IO ON; GO SET STATISTICS TIME ON; GO SELECT * INTO [Sales].[Copy_Orders] FROM [Sales].[Orders]; GO SELECT [CustomerID], [OrderID], [OrderDate] FROM [Sales].[Copy_Orders] WHERE [CustomerID] > 550; GO
همانطور که مشاهده میکنید، برای برآورده کردن قسمت where این کوئری، یک Table Scan صورت گرفتهاست؛ چون این جدول کپی، به همراه هیچ ایندکسی نیست. به همین جهت برای یافتن رکوردهای مدنظر، راه دیگری بجز اسکن کل جدول بانک اطلاعاتی وجود ندارد که بسیار ناکارآمد است.
همچنین اگر به برگهی messages دقت کنیم، با توجه به روشن بودن STATISTICS IO، میزان logical reads نیز قابل مشاهدهاست:
(33035 rows affected) Table 'Copy_Orders'. Scan count 1, logical reads 689, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times: CPU time = 79 ms, elapsed time = 762 ms.
بررسی عملگر Index Seek در یک Query Plan
اکنون سؤال اینجا است که آیا میتوان این وضعیت را بهبود بخشید؟
بله. برای این منظور یک NONCLUSTERED INDEX را بر روی جدول کپی، ایجاد میکنیم؛ به نحوی که CustomerID لحاظ شدهی در قسمت where کوئری را پوشش دهد:
CREATE NONCLUSTERED INDEX [IX_Copy_Orders_CustomerID] ON [Sales].[Copy_Orders] ( [CustomerID] ) INCLUDE ( [OrderID], [OrderDate] ); GO
در ادامه مجددا همان کوئری را اجرا میکنیم:
SELECT [CustomerID], [OrderID], [OrderDate] FROM [Sales].[Copy_Orders] WHERE [CustomerID] > 550; GO
اینبار عملگر Table Scan قبلی به یک عملگر Index Seek بر روی NONCLUSTERED INDEX تعریف شده، تغییر کردهاست و اگر به آمار I/O آن دقت کنیم، logical reads 106 قابل مشاهدهاست که بهبود قابل ملاحظهای است نسبت به عدد 689 قبلی.
بررسی عملگر Clustered index scan در یک Query Plan
در ادامه همین کوئری را بر روی جدول [Sales].[Orders] اصلی اجرا میکنیم:
SELECT [CustomerID], [OrderID], [OrderDate] FROM [Sales].[Orders] WHERE [CustomerID] > 550; GO
اجرای کوئری فوق، چنین کوئری پلنی را تولید میکند:
جدول [Sales].[Orders]، یک CLUSTERED INDEX را بر روی [OrderID] دارد و یک NONCLUSTERED INDEX را بر روی [CustomerID].
در کوئری پلن تولید شده، یک Clustered index scan مشاهده میشود. علت اینجا است که هرچند در جدول [Sales].[Orders] یک NONCLUSTERED INDEX بر روی [CustomerID] تعریف شدهاست:
CREATE NONCLUSTERED INDEX [FK_Sales_Orders_CustomerID] ON [Sales].[Orders] ( [CustomerID] ASC )
بنابراین وجود عملگر Clustered index scan در یک کوئری پلن، یعنی نیاز به خواندن و اسکن کل جدول وجود دارد. برای اثبات آن، همین کوئری قبلی را که بر روی [Sales].[Orders] انجام دادیم، اینبار بدون قسمت where آن اجرا کنید. یعنی کوئری بر روی کل جدول انجام شود:
SELECT [CustomerID], [OrderID], [OrderDate] FROM [Sales].[Orders]
سؤال: آیا Clustered index scan همواره کل یک جدول را اسکن میکند؟
پاسخ: خیر. اگر یک کوئری برای مثال دارای top/min/max باشد، کل جدول اسکن نخواهد شد:
SELECT TOP 10 [CustomerID], [OrderID], [OrderDate] FROM [Sales].[Orders] WHERE [CustomerID] > 550;
هرچند در اینجا هم یک Clustered index scan صورت گرفته، اما اگر به برگهی messages آن مراجعه کنیم، آمار I/O آن بیانگر تنها logical reads 5 است که معادل اسکن کل جدول نیست:
(10 rows affected) Table 'Orders'. Scan count 1, logical reads 5, physical reads 0, read-ahead reads 510, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
مقایسهی عملگرهای Index Scan و Index Seek
ابتدا کوئری زیر را اجرا میکنیم:
SELECT [CustomerID], [OrderID] FROM [Sales].[Orders] WHERE [OrderID] > 30000;
در این جدول ایندکسی بر روی CustomerID وجود دارد و همچنین کلید اصلی جدول، OrderID است.
پس از اجرای این کوئری، به کوئری پلن زیر خواهیم رسید:
که بیانگر یک Index Scan است و نکتهی جالب آن، استفادهی از ایندکس FK_Sales_Orders_CustomerID میباشد (نام این شیء، ذیل آیکن عملگر، مشخص است). یعنی SQL Server در اینجا از یک non-clustered index تعریف شدهی بر روی CustomerID استفاده کردهاست.
اکنون اگر OrderID را تغییر دهیم چه اتفاقی رخ میدهد؟
SELECT [CustomerID], [OrderID] FROM [Sales].[Orders] WHERE [OrderID] > 60000;
در این مثال با دو ورودی مختلف، دو کوئری پلن مختلف تولید شدهاست؛ که مرتبط است با میزان اطلاعاتی که قرار است بازگشت داده شود.
اگر این دو کوئری را با هم اجرا کنیم (در طی یک batch)، به پلن مقایسهای زیر خواهیم رسید که در آن هزینهی Index Scan بیشتر است از clustered index seek:
به همراه آمار CPU و I/O ای به صورت زیر که اولی مرتبط است با index scan و دومی با clustered index seek:
(43595 rows affected) Table 'Orders'. Scan count 1, logical reads 191, physical reads 1, read-ahead reads 182, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. SQL Server Execution Times: CPU time = 31 ms, elapsed time = 754 ms. (13595 rows affected) Table 'Orders'. Scan count 1, logical reads 131, physical reads 0, read-ahead reads 127, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. SQL Server Execution Times: CPU time = 16 ms, elapsed time = 276 ms.
فیلدهای XML از سال 2005 به امکانات توکار SQL Server اضافه شدهاند و بسیاری از مزایای دنیای NoSQL را درون SQL Server رابطهای مهیا میسازند. برای مثال با تعریف یک فیلد به صورت XML، میتوان از هر ردیف به ردیفی دیگر، اطلاعات متفاوتی را ذخیره کرد؛ به این ترتیب امکان کار با یک فیلد که میتواند اطلاعات یک شیء را قبول کند و در حقیقت امکان تعریف اسکیمای پویا و متغیر، در کنار امکانات یک بانک اطلاعاتی رابطهای که از اسکیمای ثابت پشتیبانی میکند، میسر میشود.
چندین روش برای انجام مقایسه حساس به حروف کوچک و بزرگ (case sensitive) در SQL Server وجود دارد که در ادامه آنها را مرور خواهیم کرد:
ابتدا جدول موقتی زیر را جهت آزمایشات بعدی در نظر بگیرید
CREATE TABLE #tblTest
(
f1 NVARCHAR(50)
)
INSERT INTO #tblTest (f1) VALUES('Test1')
INSERT INTO #tblTest (f1) VALUES('TEST1')
الف) استفاده از collation صحیح
عموما هنگام نصب اس کیوال سرور از collation غیرحساس به کوچکی و بزرگی حروف استفاده میشود و این مورد سبب میشود که پیش فرض ایجاد دیتابیسها نیز به همین صورت باشد (هر چند کاملا قابل کنترل و تنظیم است). به صورت پویا میتوان این collation را در کوئریها نیز اعمال نمود. برای مثال:
SELECT f1 FROM #tblTest WHERE f1 COLLATE SQL_Latin1_General_CP1_CS_AS = 'Test1'
ب) استفاده از تابع BINARY_CHECKSUM اس کیوال سرور (نوعی الگوریتم ویژه، شبیه به امضای دیجیتال و هش کردن اطلاعات است)
SELECT f1 FROM #tblTest WHERE BINARY_CHECKSUM(f1) = BINARY_CHECKSUM('Test1')
SELECT f1 FROM #tblTest WHERE hashbytes('md5',f1) = hashbytes('md5',N'Test1')
SELECT f1 FROM #tblTest WHERE convert(varbinary(50),f1) = convert(varbinary(50),N'Test1')