MySQL / MySQL шилдэг туршлага

MySQL шилдэг туршлага

MySQL дээр зөв, аюулгүй, хурдан аппликейшн бичихийн тулд баримтлах дүрмүүд. Эдгээр туршлага нь жижиг төслөөс томд шилжих үед чухал болно.

Нэрлэлтийн дүрэм

Тогтмол дүрэм баримтлах нь багаар ажиллах болон ирээдүйд кодоо уншихад дэм болно.

Хүснэгтийн нэр

sql
-- ЗӨВ: олон тооны нэр, snake_case
CREATE TABLE users (...);
CREATE TABLE order_items (...);
CREATE TABLE product_categories (...);

-- БУРУУ: ганц тооны нэр, camelCase, холимог
CREATE TABLE User (...);
CREATE TABLE orderItem (...);
CREATE TABLE ProductCategory (...);

Баганын нэр

sql
-- ЗӨВ: snake_case, тодорхой нэр
CREATE TABLE users (
    id          INT AUTO_INCREMENT PRIMARY KEY,
    first_name  VARCHAR(100) NOT NULL,
    email       VARCHAR(255) UNIQUE NOT NULL,
    created_at  DATETIME DEFAULT CURRENT_TIMESTAMP,
    is_active   BOOLEAN DEFAULT TRUE
);

-- БУРУУ: товчилсон, тодорхойгүй нэр
CREATE TABLE users (
    uid  INT,
    fn   VARCHAR(100),
    em   VARCHAR(255),
    dt   DATETIME,
    act  TINYINT
);

Foreign key нэр

sql
-- Дүрэм: [хүснэгтийн нэр]_id
CREATE TABLE orders (
    id         INT AUTO_INCREMENT PRIMARY KEY,
    user_id    INT NOT NULL,           -- users хүснэгтийн FK
    address_id INT,                    -- addresses хүснэгтийн FK
    FOREIGN KEY (user_id) REFERENCES users(id)
);

Index стратеги

Index нь хайлтыг хурдасгах боловч INSERT/UPDATE/DELETE-ийг удаашруулна. Зөв газар тавих нь чухал.

Index тавих газар

sql
-- 1. WHERE нөхцөлд байнга хэрэглэгдэх баганад
CREATE INDEX idx_orders_status ON orders(status);

-- 2. JOIN дахь foreign key-д
CREATE INDEX idx_order_items_order_id ON order_items(order_id);
CREATE INDEX idx_order_items_product_id ON order_items(product_id);

-- 3. ORDER BY байнга хэрэглэгдэх баганад
CREATE INDEX idx_products_price ON products(price);

-- 4. Хайлт хийгддэг текст баганад
CREATE INDEX idx_users_email ON users(email);

Composite index

Хоёр ба түүнээс дээш баганыг хамт хайх тохиолдолд:

sql
-- status болон created_at-аар хамт хайх тохиолдолд
CREATE INDEX idx_orders_status_date ON orders(status, created_at);

-- Зөв: эхний баганаар ялгана
SELECT * FROM orders WHERE status = 'pending' ORDER BY created_at;

-- Хэрэглэгдэхгүй: эхний баганыг орхисон
SELECT * FROM orders WHERE created_at > '2024-01-01';

Index тавихгүй байх газар

sql
-- Цөөн өөр утгатай баганад тавихгүй (boolean, gender гэх мэт)
-- Байнга өөрчлөгддөг баганад тавихгүй
-- Маш жижиг хүснэгтэд тавихгүй (1000-аас цөөн мөр)

Query оновчлол

SELECT * ашиглахгүй

sql
-- БУРУУ: бүх баганыг татна, удаан
SELECT * FROM users;

-- ЗӨВ: хэрэгтэй баганыг л татна
SELECT id, name, email FROM users;

LIKE хайлтын зарчим

sql
-- БУРУУ: index ашиглахгүй (эхэнд % байна)
SELECT * FROM products WHERE name LIKE '%утас%';

-- ХАРЬЦАНГУЙ САЙН: index ашиглана (эхэнд % байхгүй)
SELECT * FROM products WHERE name LIKE 'утас%';

-- Дунд болон эцэст хайх бол FULLTEXT index ашигла

IN vs OR

sql
-- САЙН: IN илүү хурдан
SELECT * FROM orders WHERE status IN ('pending', 'processing', 'shipped');

-- УДААН: OR дараалалтай шалгана
SELECT * FROM orders
WHERE status = 'pending' OR status = 'processing' OR status = 'shipped';

Тооцооллыг WHERE-д хийхгүй

sql
-- БУРУУ: баганад функц ашигласнаар index ажиллахгүй
SELECT * FROM orders WHERE YEAR(created_at) = 2024;

-- ЗӨВ: баганыг шууд харьцуулна
SELECT * FROM orders
WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';

Аюулгүй байдал

Prepared statement үргэлж ашигла

sql
-- БУРУУ: SQL Injection-д өртөмтгий
SET @name = 'Болд';
SELECT * FROM users WHERE name = @name;   -- шууд залгахгүй!

-- ЗӨВ: application layer-д prepared statement ашигла
-- (Node.js: pool.execute('SELECT * FROM users WHERE name = ?', [name]))
-- (Python: cursor.execute('SELECT * FROM users WHERE name = %s', (name,)))

Хэрэглэгчийн эрхийг хязгаарла

sql
-- Вэб аппликейшны хэрэглэгч зөвхөн хэрэгтэй эрхтэй байх ёстой
CREATE USER 'webapp'@'localhost' IDENTIFIED BY 'хүчтэй_нууц_үг';

-- Зөвхөн DML эрх (DDL буюу DROP, CREATE эрхгүй)
GRANT SELECT, INSERT, UPDATE, DELETE ON myshop.* TO 'webapp'@'localhost';

FLUSH PRIVILEGES;

-- root хэрэглэгчийг аппликейшнд ашиглахгүй!

Нууц үгийг хэзээ ч кодонд бичихгүй

javascript
-- БУРУУ: нууц үг кодонд харагдана
const conn = mysql.createConnection({ password: 'миний_нууц_үг' });

-- ЗӨВ: орчны хувьсагчаас авна
const conn = mysql.createConnection({ password: process.env.DB_PASSWORD });
bash
-- .env файлд хадгална, .gitignore-д нэмнэ
DB_PASSWORD=хүчтэй_нууц_үг

Мэдээллийг зөв засварлах

sql
-- Бодит устгахын оронд soft delete ашигла
ALTER TABLE users ADD COLUMN deleted_at DATETIME NULL;

-- Устгах үед:
UPDATE users SET deleted_at = NOW() WHERE id = ?;

-- Хайхдаа:
SELECT * FROM users WHERE deleted_at IS NULL;

Soft delete нь өгөгдлийг алдахгүй, хэрэгцээтэй бол сэргээх боломжтой.

Backup

mysqldump — бүтэн backup

bash
-- Нэг database backup хийх
mysqldump -u root -p myshop > myshop_backup_2024.sql

-- Бүх database backup хийх
mysqldump -u root -p --all-databases > all_backup.sql

-- Тогтмол автомат backup (Linux cron)
-- Энэ мөрийг crontab -e-д нэмнэ:
-- 0 2 * * * mysqldump -u root -pНУУЦ_ҮГ myshop > /backup/myshop_$(date +%F).sql

Restore

bash
mysql -u root -p myshop < myshop_backup_2024.sql

Зөвхөн бүтцийг backup хийх

bash
-- Өгөгдөлгүй зөвхөн CREATE TABLE бүтэц
mysqldump -u root -p --no-data myshop > schema_only.sql

Schema дизайны дүрмүүд

sql
-- 1. Primary key үргэлж байх
-- 2. NOT NULL-ийг default болгох, NULL зөвшөөрөх шаардлагатай үед л NULL
-- 3. Валютыг DECIMAL(15, 2)-ээр хадгала, FLOAT биш
-- 4. Огноог DATETIME эсвэл DATE-ээр хадгала, VARCHAR биш
-- 5. Хүснэгт бүрт created_at нэмэх

CREATE TABLE products (
    id          INT AUTO_INCREMENT PRIMARY KEY,
    name        VARCHAR(255) NOT NULL,
    price       DECIMAL(15, 2) NOT NULL,     -- валют
    stock       INT NOT NULL DEFAULT 0,
    is_active   BOOLEAN NOT NULL DEFAULT TRUE,
    created_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
                ON UPDATE CURRENT_TIMESTAMP
);

EXPLAIN ашиглан query шалгах

Удаан query-г шинжлэхийн тулд EXPLAIN ашиглана:

sql
EXPLAIN SELECT u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id;

Гаралтад анхаарах зүйлс:

код
type = ALL    → бүх мөрийг уншина (удаан) — index хэрэгтэй
type = ref    → index ашиглана (хурдан)
type = const  → нэг мөр олсон (хамгийн хурдан)

rows = 10000  → маш олон мөр шалгана — query оновчлох хэрэгтэй

Дараагийн хичээлд:

Эцсийн төсөл — онлайн дэлгүүрийн бүрэн database дизайн: users, products, categories, orders, order_items хүснэгтүүд, CRUD query, JOIN, aggregate, transaction.