فصل ۳: طراحی دیتابیس — نوع داده، کلیدها و نرمال‌سازی

انواع عددی و متنی: INT و UNSIGNED، DECIMAL برای پول، VARCHAR با utf8mb4

نوع داده یک قرارداد است، نه فقط یک ظرف

نوع درست ستون سه کار می‌کند: جلوی داده‌ی غلط را می‌گیرد، فضای دیسک و حافظه را کم می‌کند (ایندکس‌ها کوچک‌تر و سریع‌تر می‌شوند) و مقایسه‌ها را درست انجام می‌دهد. تغییر نوع ستون روی جدول چندمیلیونی بعداً یعنی بازسازی کل جدول؛ پس از اول درست انتخاب کنید.

اعداد صحیح

نوعبایتبازه‌ی UNSIGNEDمثال کاربرد
TINYINT10 تا 255وضعیت، پرچم بولی
SMALLINT20 تا 65,535ابعاد به سانتی‌متر، تراکم شانه
MEDIUMINT30 تا 16.7 میلیونجدول‌های مرجع متوسط
INT40 تا 4.29 میلیاردشناسه‌ی اغلب جدول‌ها
BIGINT80 تا 1.8×10^19شناسه‌ی لاگ و رویداد، مبلغ ریالی به‌صورت عدد صحیح

عدد داخل پرانتز در INT(11) «عرض نمایش» است، نه محدودیت؛ در MySQL 8 منسوخ شده و بی‌اثر است. BOOLEAN هم فقط نام دیگر TINYINT(1) است و مقدار 7 را هم می‌پذیرد.

پول: DECIMAL، هرگز FLOAT

SELECT 0.1 + 0.2 = 0.3;                                   -- 1 (لیترال‌ها DECIMAL هستند)
SELECT CAST(0.1 AS DOUBLE) + CAST(0.2 AS DOUBLE) = 0.3;   -- 0 !

CREATE TABLE invoices (
  id         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  amount     DECIMAL(15,0) NOT NULL,      -- ریال؛ تا 999 تریلیون
  tax_rate   DECIMAL(5,2)  NOT NULL DEFAULT 10.00,
  usd_rate   DECIMAL(12,2) NULL,          -- نرخ ارز با دو رقم اعشار
  weight_kg  FLOAT NULL                   -- اندازه‌گیری فیزیکی؛ خطای کوچک مهم نیست
);

FLOAT و DOUBLE اعداد را به‌صورت تقریبی دودویی نگه می‌دارند؛ جمع هزاران ردیف، اختلاف چندریالی می‌سازد که حسابدار هرگز نمی‌بخشد. DECIMAL(M,D) دقیق است: M کل ارقام و D ارقام اعشار. برای ریال D=0 کافی است؛ اگر سیستم تومان با اعشار (مثل ۱۲٬۵۰۰٫۵) لازم دارد، ریال ذخیره کنید و در نمایش تبدیل کنید.

متن: CHAR، VARCHAR و TEXT

  • VARCHAR(n): n به کاراکتر است، نه بایت. در utf8mb4 هر کاراکتر تا ۴ بایت جا می‌گیرد.
  • CHAR(n): طول ثابت؛ برای کدهای هم‌طول مثل کد کشور. در utf8mb4 مزیت فضایی ندارد.
  • TEXT (64KB)، MEDIUMTEXT (16MB)، LONGTEXT (4GB): برای متن بلند؛ بیرون از ردیف اصلی ذخیره می‌شوند، پیش‌فرض ثابت (جز عبارت در پرانتز در 8.0.13 به بعد) ندارند و فقط با پیشوند ایندکس می‌شوند.
CREATE TABLE customers (
  id          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  full_name   VARCHAR(120) NOT NULL,
  mobile      CHAR(11)     NOT NULL,         -- 09121234567 ؛ رشته، نه عدد!
  national_id CHAR(10)     NULL,             -- صفر ابتدایی حفظ می‌شود
  notes       TEXT         NULL,
  UNIQUE KEY uq_mobile (mobile)
);

نکته‌هایی که کمتر کسی می‌داند

  • مجموع طول همه‌ی ستون‌های VARCHAR یک ردیف (بر حسب بایت بیشینه) نباید از 65,535 بایت بگذرد؛ در utf8mb4 یعنی حدود 16,000 کاراکتر برای کل ردیف. به همین دلیل VARCHAR(20000) خطای Row size too large می‌دهد.
  • کلید ایندکس در InnoDB حداکثر 3072 بایت است؛ یعنی VARCHAR(768) در utf8mb4. ایندکس ترکیبی هم مجموع بایت‌ها را حساب می‌کند.
  • شماره‌ی موبایل، کد ملی و کد پستی را هرگز INT نکنید؛ صفر ابتدایی کد ملی حذف می‌شود و روی آن محاسبه‌ی ریاضی هم انجام نمی‌دهید.
  • UNSIGNED روی DECIMAL و FLOAT در 8.0.17 منسوخ شده؛ برای جلوگیری از مبلغ منفی از CHECK (amount >= 0) استفاده کنید.
  • تفریق دو ستون UNSIGNED که نتیجه‌اش منفی شود، خطای BIGINT UNSIGNED value is out of range می‌دهد؛ مثلاً stock - reserved. یکی را CAST به SIGNED کنید.

برای ذخیره‌ی پیشرفت و شرکت در آزمون، وارد شوید — رایگان است.