برنامه نویسی

پایگاه داده و SQL مقدماتی

پایگاه داده برنامه ای است که داده های شما را در جدول هایی با سطر و ستون نگه می دارد و اجازه می دهد چند برنامه همزمان و بدون خراب کردن کار همدیگر از آن بخوانند و در آن بنویسند. SQL هم زبانی است که با آن می گویید چه چیزی می خواهید: <code>SELECT</code> برای خواندن، <code>INSERT</code> برای افزودن و <code>UPDATE</code> برای تغییر.

  • درس ۱۰ از ۱۴
  • مقدماتی
  • رایگان، بدون ثبت نام

سه جدول، و ستونی که آنها را به هم وصل می کند

  1. جدول کاربران

    ۲
    • id = ۴۲، مریم

      کلید اصلی: یکتا و بدون تغییر

    • id = ۴۳، سعید

      اسم و ایمیل عوض می شوند، id نه

  2. جدول سفارش ها

    ۲
    • سفارش ۱۰۰۱، user_id = ۴۲

      کلید خارجی: نام مشتری تکرار نمی شود

    • سفارش ۱۰۰۲، user_id = ۴۲

      یک کاربر می تواند چند سفارش داشته باشد

  3. جدول محصولات

    ۱
    • id = ۷، قیمت و نام

      قیمت اینجا نوشته می شود، نه داخل هر سفارش

هر کارت یک سطر است و هر ستون یک جدول. این تصویر رابطه ها را نشان می دهد و نه ایندکس ها و قیدها را، که همان چیزهایی اند که سرعت و درستی از آنها می آید.

آخرین بررسی: فکت ها و نام ابزارهای این درس در همین تاریخ با منابعشان بازبینی شده اند.

چرا یک فایل متنی کافی نیست؟

می شود اطلاعات کاربران را در یک فایل متنی نگه داشت و برای صد ردیف هم خوب کار می کند. سه چیز آن را می شکند و هر سه در همان ماه اول پیش می آیند.

اول همزمانی: وقتی دو نفر در یک لحظه ثبت نام می کنند، دو برنامه همزمان می خواهند در یک فایل بنویسند و یکی از دو نوشته گم می شود. دوم جستجو: پیدا کردن کاربری با ایمیل مشخص در یک فایل یعنی خواندن کل فایل، و این با بزرگ شدن فایل کندتر و کندتر می شود. سوم درستی داده: هیچ چیز در یک فایل متنی جلوی ثبت دو کاربر با یک ایمیل را نمی گیرد.

پایگاه داده برنامه ای است که این سه مساله را حل می کند: قفل گذاری برای نوشتن همزمان، ایندکس برای پیدا کردن سریع، و قید برای اینکه داده نامعتبر اصلا وارد نشود. شما با آن حرف نمی زنید که فایل ذخیره کند؛ حرف می زنید که این سه ضمانت را بگیرید.

و یک نکته که کمتر گفته می شود: پایگاه داده جای فایل را نمی گیرد. عکس ها و ویدیوها و فایل های دانلودی معمولا روی دیسک می مانند و فقط نشانی شان در جدول ثبت می شود. وردپرس هم همین کار را می کند و به همین دلیل پشتیبان گرفتن از پایگاه داده، به تنهایی پشتیبان سایت نیست.

جدول، سطر، ستون و آن ستون id

یک جدول شبیه یک برگه صفحه گسترده است: ستون ها می گویند چه چیزهایی ذخیره می شود و هر سطر یک رکورد است. جدول کاربران ستون های id و نام و ایمیل دارد و هر سطرش یک کاربر است. تا اینجا هیچ چیز پیچیده ای نیست.

ستون id اما نقش خاصی دارد و اسمش کلید اصلی است: مقداری که در کل جدول یکتاست و هیچ وقت هم عوض نمی شود. دلیل وجودش این است که هیچ چیز دیگری قابل اتکا نیست؛ آدم ها اسمشان تکراری است، ایمیلشان را عوض می کنند و شماره شان را از دست می دهند. برای همین سفارش ها به جای نام مشتری، شماره id او را نگه می دارند و به آن کلید خارجی می گویند.

حالا یک مثال واقعی که فرض ذهنی رایج را خراب می کند. جدول اصلی همین سایت که وردپرس به آن posts می گوید، روزی که این درس بررسی شد ۷۸۴ سطر داشت، ولی این ۷۸۴ سطر ۲۴ نوع مختلف چیز بودند: ۲۶۳ نوشته بلاگ، ۲۴۰ فایل رسانه، ۳۷ برگه، و بقیه پروژه و پیام و آگهی و چند چیز دیگر.

درسی که در این عدد هست به کار هر کسی می آید: یک جدول یک شکل است، نه یک موضوع. هر چیزی که همان ستون ها را لازم داشته باشد می تواند در همان جدول بنشیند و با یک ستون دیگر از بقیه جدا شود. نتیجه عملی اش هم این است که SELECT روی چنین جدولی بدون شرط، مخلوطی از چهار چیز بی ربط به شما می دهد.

سه دستوری که بیشتر کارها با آنها انجام می شود

SQL زبان درخواست است، نه زبان دستور دادن قدم به قدم. شما نمی گویید چطور بگرد؛ می گویید چه چیزی می خواهم، و پایگاه داده خودش راه رسیدن را انتخاب می کند.

SELECT name, email FROM users WHERE city = 'تهران';

INSERT INTO users (name, email, city) VALUES ('مریم', 'm@example.com', 'شیراز');

UPDATE users SET city = 'اصفهان' WHERE id = 42;

سه چیز در همین سه خط هست که ارزش دیدن دارد. SELECT می گوید کدام ستون ها، FROM می گوید از کدام جدول و WHERE می گوید کدام سطرها. اگر ستون ها را ندانید SELECT * همه را می آورد، که برای نگاه کردن خوب است و در کد برنامه عادت بدی است، چون روزی که یک ستون اضافه شود برنامه شما چیزی می گیرد که انتظارش را نداشته.

خطرناک ترین کلمه این صفحه هم همان WHERE است، به خاطر نبودنش. UPDATE users SET city = 'اصفهان'; یک خطای نحوی نیست؛ کاملا معتبر است و شهر همه کاربران را عوض می کند. مستندات هر دو موتور MySQL و MariaDB این را صریح نوشته اند و حتی حالتی دارند که چنین دستوری را رد می کند.

عادتی که این را حل می کند یک خط بیشتر تایپ کردن است: هر UPDATE یا DELETE را اول به شکل SELECT با همان شرط بنویسید و ببینید چند سطر برمی گردد. اگر عدد آن چیزی نبود که فکر می کردید، شرط شما اشتباه است و تازه فهمیدید، نه بعد از اینکه داده ها رفته باشند.

قبل از زدن یک UPDATEبرای داده واقعی

انجام بدهید

  • همان شرط را اول به شکل SELECT بزنید و تعداد سطرها را ببینید
  • اگر عدد با انتظار شما نمی خواند، شرط را درست کنید نه دستور را
  • روی یک نسخه از داده تمرین کنید، نه روی داده زنده
  • مقدار ورودی کاربر را با جای نگهدار بفرستید، نه با چسباندن

نکنید

  • UPDATE یا DELETE بدون WHERE
  • کوئری ساخته شده از چسباندن متن کاربر
  • اجرای دستوری که کسی دیگر نوشته و شما نمی فهمید

این فهرست جای پشتیبان را نمی گیرد. UPDATE بدون شرط روی داده واقعی، بدون پشتیبان، برگشت پذیر نیست.

پایگاه داده و کش را با هم اشتباه نگیرید

روی همین سرور دو چیز کنار هم کار می کنند و کار هر کدام کاملا فرق دارد. یکی MariaDB است، از خانواده MySQL، که همه داده های ماندگار سایت را نگه می دارد. دیگری ردیس است که یک کش شی است و نتیجه پرسش هایی را که تازه از پایگاه داده گرفته شده در حافظه نگه می دارد تا درخواست بعدی همان را دوباره نپرسد.

تفاوتشان را می شود در یک جمله گفت: اگر کش را کامل پاک کنیم هیچ چیزی از دست نمی رود و فقط چند درخواست بعدی کندتر می شوند، چون همه چیز دوباره از پایگاه داده ساخته می شود. اگر پایگاه داده را از دست بدهیم، همه چیز رفته است.

از همین یک جمله یک قاعده عملی درمی آید که همه جا به کارتان می آید: هیچ وقت چیزی را فقط در کش نگذارید. کش حق دارد هر لحظه خالی باشد، چه به خاطر ری استارت، چه به خاطر پر شدن حافظه، و برنامه ای که به بودن یک مقدار در کش تکیه کند، روزی که خالی است خطا می دهد. کش برای سرعت است، نه برای نگهداری.

و همین تفکیک است که تصمیم های میزبانی را روشن می کند: پایگاه داده به دیسک سریع و پشتیبان منظم نیاز دارد و کش به حافظه. روی هاست وردپرسی که خودمان راه انداخته ایم هر دو کنار هم اند، ولی فقط یکی از آن دو پشتیبان گرفته می شود و باید هم همین طور باشد.

ماندگاری در برابر سرعت: دو کار، نه دو گزینه

پایگاه داده

  • مرجع نهایی داده: اگر اینجا نباشد، نیست
  • روی دیسک می ماند و پشتیبان گرفته می شود
  • قید و کلید، جلوی داده نامعتبر را می گیرد

کش

  • کپی موقت از چیزی که همین حالا ساخته شده
  • در حافظه است و حق دارد هر لحظه خالی باشد
  • پاک کردنش چیزی را از بین نمی برد، فقط کند می کند

این دو رقیب هم نیستند و یکی جای دیگری را نمی گیرد. اشتباه رایج، گذاشتن چیزی فقط در کفه راست است.

کوئری امن یعنی چه؟

خطرناک ترین اشتباه در کار با پایگاه داده این است که کوئری را با چسباندن متنی که کاربر وارد کرده بسازید. وقتی جمله SQL از دو تکه ساخته شود، متن کاربر و دستور، پایگاه داده هیچ راهی ندارد بفهمد کدام قسمت داده بوده و کدام دستور. اسم این دسته آسیب پذیری تزریق SQL است.

راه حلش هم قدیمی و ساده است و همه جا یکی است: جای مقدار در متن کوئری یک جای نگهدار بگذارید و مقدار را جدا تحویل بدهید. آن وقت هر چیزی که کاربر نوشته باشد، حتی اگر خودش شبیه یک دستور SQL باشد، فقط به عنوان یک رشته خوانده می شود. در وردپرس متد prepare روی شی wpdb همین کار را می کند و مستندات رسمی وردپرس هم استفاده از آن را قبل از هر کوئری با ورودی کاربر لازم می داند.

یک نکته عملی که همین جا وقت زیادی برایتان می خرد: در بیشتر پروژه ها شما اصلا SQL خام نمی نویسید. وردپرس و فریمورک ها یک لایه واسط دارند که کوئری را برای شما می سازد و امن هم می سازد. دانستن SQL برای این است که بفهمید آن لایه چه چیزی تولید کرده و چرا کند است، نه برای اینکه هر کوئری را دستی بنویسید.

مسیر سریع با هوش مصنوعی

تبدیل جمله فارسی به کوئری، کاری است که مدل های زبانی به طرز عجیبی خوب انجام می دهند و برای همین هم خطرناک است: کوئری غلطی که اجرا می شود و جواب معقول می دهد، از کوئری ای که خطا می دهد بدتر است. رویه ای که ما استفاده می کنیم دو تفاوت با «برایم یک کوئری بنویس» دارد: اول ساختار جدول را می دهیم و نه داده را، و دوم همیشه اول SELECT می خواهیم، حتی وقتی هدف نهایی تغییر داده است.

  1. ساختار جدول را بگیرید و همان را بفرستید، نه سطرها را. یک <code>SHOW CREATE TABLE</code> یا فهرست ستون ها با نوعشان کافی است و هیچ داده مشتری از دستگاه شما بیرون نمی رود.
  2. با دستور زیر اول یک SELECT بخواهید که دقیقا همان سطرهایی را نشان بدهد که قرار است عوض شوند، به همراه یک شمارش.
  3. آن SELECT را اجرا کنید و عدد را با انتظار خودتان بسنجید. اگر نمی خواند، همین جا کار تمام است و باید شرط را عوض کنید؛ به سراغ دستور دوم نروید.
  4. تازه بعد از آن UPDATE را بخواهید، با همان شرط بی کم و کاست، و روی داده واقعی فقط وقتی بزنید که پشتیبان تازه دارید.

نسخه آماده کپی

ساختار جدول من این است:

{خروجی SHOW CREATE TABLE یا فهرست ستون ها با نوعشان}

کاری که می خواهم انجام شود:
{به فارسی ساده بنویسید، مثلا: شهر همه کاربرانی که در شش ماه اخیر سفارشی نداشته اند را خالی کن}

به همین ترتیب جواب بده:
۱. فرض هایی که برای نوشتن این کوئری لازم داری و در ساختار بالا نیست را فهرست کن. اگر فرضی لازم بود، اول بپرس و کوئری ننویس.
۲. یک SELECT بده که دقیقا همان سطرهایی را برگرداند که قرار است تغییر کنند، به علاوه یک شمارش سطرها.
۳. بعد از آن UPDATE را بده، با همان شرط SELECT بدون هیچ تغییری.
۴. بگو اگر شرط اشتباه باشد، بدترین حالتی که این دستور می سازد چیست.

هیچ داده واقعی به تو نداده ام و لازم هم نداری. اگر برای جواب دادن به داده نیاز داری، بگو چرا.

قبل از اعتماد به خروجی: مدل ساختار جدول را می بیند و معنای داده را نمی بیند. نمی داند در پروژه شما ستون خالی یعنی «هنوز پر نشده» یا «عمدا حذف شده»، و نمی داند تریگر یا کدی هست که با همین تغییر کار دیگری هم انجام بدهد. عددی هم که درباره تعداد سطرها بگوید حدس است؛ عدد واقعی همان چیزی است که SELECT خودتان برمی گرداند. و مرز آخر تکراری است ولی مهم: روی داده زنده، تنها چیزی که یک UPDATE اشتباه را برمی گرداند پشتیبان است، نه مدل.

هوش مصنوعی در این کار

برای این موضوع، مدل زبانی واقعا وقت شما را برمی گرداند: نوشتن یک کوئری از روی توضیح فارسی، توضیح دادن کوئری ای که کس دیگری نوشته، و پیدا کردن اینکه چرا یک کوئری کند است. موضع ما این است که مرز را روی نوع دستور بگذارید و نه روی سختی کار: برای <code>SELECT</code> با خیال راحت از مدل کمک بگیرید و خروجی را اجرا کنید، و برای هر دستوری که داده را عوض می کند اول SELECT معادلش را ببینید.

ابزارهایی که واقعا کمک می کنند

  • Claude برای دستور همین صفحه خوب جواب می دهد، چون وقتی صریح بگویید فرض هایت را اول فهرست کن، واقعا فهرست می کند به جای اینکه حدس بزند. ایران در هیچ کدام از دو فهرست کشورهای پشتیبانی شده انتروپیک نیست؛ این را از صفحه خود انتروپیک خوانده ایم.
  • ChatGPT برای توضیح دادن یک کوئری ناآشنا و ترجمه اش به زبان آدمیزاد گزینه رایجی است. درباره دسترسی از ایران چیزی نمی نویسیم چون بررسی اش نکرده ایم: صفحه کشورهای پشتیبانی شده اوپن ای آی، مثل بقیه آن دامنه، به سرور ما ۴۰۳ می دهد و ادعای بی منبع نمی نویسیم.
  • Gemini کوئری های ساده و تکراری را با یک مدل سریع و ارزان بنویسید؛ همین جا جایش است. برای کوئری روی جدولی که ساختار پیچیده دارد، مدل قوی تر جواب بهتری می دهد. صفحه خود گوگل می گوید اپ وب جمنای در بیش از دویست و سی کشور و منطقه کار می کند و ایران در آن فهرست نیست.

کجا نتیجه معکوس می دهد

خطر اصلی این موضوع، کوئری غلطی است که خطا نمی دهد. یک شرط جا افتاده یا یک OR به جای AND، دستوری می سازد که اجرا می شود و جواب معقولی هم برمی گرداند؛ فقط جواب سوال شما نیست. وقتی این اتفاق روی SELECT بیفتد یک گزارش اشتباه می گیرید، و وقتی روی UPDATE بیفتد داده را عوض کرده اید. مستندات MySQL و MariaDB جدی بودن حالت بدون شرط را خودشان نوشته اند و همین است که ترتیب «اول SELECT» را ضروری می کند.
خطر دوم درباره داده است نه کوئری: سطرهای یک جدول واقعی معمولا اطلاعات آدم های واقعی اند و چسباندنشان در یک چت یعنی خارج کردن داده شخصی از کنترل شما. برای کار کردن با مدل هیچ وقت به سطرها نیاز ندارید؛ ساختار جدول کافی است و همین یک تصمیم، این خطر را کامل حذف می کند. برای اینکه بدانید هر ابزار از ایران چطور قابل پرداخت است راهنمای خرید را ببینید.

منبع ها: MySQL: the UPDATE statement MariaDB: UPDATE WordPress: wpdb prepare Anthropic: supported countries Google: where the Gemini web app is available

حد این توصیه

این درس پایگاه داده رابطه ای را در سطح مقدماتی پوشش می دهد و چند چیز را عمدا نیاورده، چون هر کدام درس خودشان را می خواهند: JOIN و کار همزمان با چند جدول، ایندکس و اینکه چرا یک کوئری کند است، نرمال سازی، تراکنش و ضمانت هایی که پایگاه داده موقع خرابی می دهد، و پایگاه داده های غیر رابطه ای که اصلا جدول ندارند. یک مرز صریح تر هم هست: دستورهای این صفحه را روی داده واقعی تمرین نکنید. یک نسخه از پایگاه داده روی کامپیوتر خودتان بسازید و آنجا هر چه خواستید خراب کنید؛ همان کاری که ما هم می کنیم.

از تجربه خود ما

عددهای این درس از همین سرور خوانده شده اند، در تاریخی که بالای صفحه به عنوان آخرین بررسی نوشته شده. پایگاه داده این سایت ۸۶ جدول دارد و جدول posts که وردپرس محتوا را در آن نگه می دارد، ۷۸۴ سطر داشت. جالبی ماجرا در ترکیب همان ۷۸۴ سطر است: ۲۴ مقدار مختلف در ستون نوع، شامل ۲۶۳ نوشته بلاگ، ۲۴۰ فایل رسانه و ۳۷ برگه، به علاوه پروژه های فریلنسری، پیام های تماس، آگهی های بازار و چند نوع دیگر که همه در همان یک جدول اند. کنار این جدول، یک کش ردیس هم روی همین سرور کار می کند و همان طور که در متن گفتیم، پاک کردن کاملش هیچ داده ای را از بین نمی برد. این دو با هم بهترین تعریف عملی از تفاوت «انبار ماندگار» و «کپی سریع» اند و برای همین هم مثال این درس شدند.

سوال هایی که واقعا پرسیده می شوند

فرق SQL با MySQL چیست؟

SQL یک زبان است و MySQL و MariaDB و PostgreSQL برنامه هایی هستند که آن زبان را می فهمند. پس چیزی که یاد می گیرید تقریبا همه جا کار می کند و فقط جزئیات هر برنامه فرق دارد. MariaDB هم انشعابی از MySQL است و دستورهای این درس در هر دو یکسان اند.

برای کار با وردپرس باید SQL بلد باشم؟

برای کار روزمره نه؛ وردپرس همه کوئری ها را خودش می سازد. لازم می شود وقتی می خواهید بفهمید چرا سایت کند شده یا یک گزارش بگیرید که هیچ افزونه ای نمی دهد. حتی همان موقع هم اول با خواندن شروع کنید و نوشتن روی داده زنده را به آخر بگذارید.

یک UPDATE اشتباه را می شود برگرداند؟

به تنهایی نه. اگر دستور را داخل یک تراکنش زده باشید و هنوز تایید نکرده باشید، می شود لغوش کرد؛ در غیر این صورت تنها راه، بازگرداندن از پشتیبان است. به همین دلیل هم روی داده واقعی، «پشتیبان تازه دارم؟» سوالی است که قبل از دستور پرسیده می شود، نه بعدش.