19-bo‘lim

Zaxira nusxa va PHP bilan ishlash

mysqldump bilan zaxira olish, tiklash, migratsiyalar hamda PDO orqali PHP dan bazaga ulanish.

🕑 15 daqiqa o‘qish 📄 689 so‘z 👁 6 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. mysqldump bilan zaxira olish
  2. Tiklash
  3. Avtomatlashtirish
  4. Migratsiyalar
  5. PHP va PDO
  6. O'qish
  7. Yozish
  8. IN bilan ishlash
  9. Tranzaksiya
  10. Ko'p qatorli qo'shish
  11. Sahifalash
  12. Xatolarni ushlash
  13. Oddiy repozitoriy klassi
  14. Xulosa

Baza qanchalik yaxshi loyihalangan bo'lmasin, zaxira nusxasiz u bir kunlik ish. Bu bo'limda ma'lumotni saqlash va uni ilovadan ishlatishni o'rganamiz.

mysqldump bilan zaxira olish #

Terminal
# Bitta baza
mysqldump -u root -p dokon > dokon_zaxira.sql

# Bir nechta baza
mysqldump -u root -p --databases dokon maktab > ikkala.sql

# Barcha bazalar
mysqldump -u root -p --all-databases > hammasi.sql

# Faqat bitta jadval
mysqldump -u root -p dokon mahsulotlar > mahsulotlar.sql

# Faqat tuzilma (ma'lumotsiz)
mysqldump -u root -p --no-data dokon > dokon_tuzilma.sql

# Faqat ma'lumot (tuzilmasiz)
mysqldump -u root -p --no-create-info dokon > dokon_malumot.sql
XAMPP da yo'l

Windows da mysqldump odatda shu yerda:

Natija
C:\xampp\mysql\bin\mysqldump.exe

To'liq yo'l bilan chaqiring yoki C:\xampp\mysql\bin ni PATH ga qo'shing.

Parol yo'q bo'lsa -p ni tashlab keting:

Terminal
mysqldump -u root dokon > dokon_zaxira.sql
BayroqVazifasi
--no-dataFaqat CREATE TABLE
--no-create-infoFaqat INSERT
--routinesProtsedura va funksiyalarni ham
--triggersTriggerlarni ham (standart yoqilgan)
--eventsRejalashtirilgan hodisalarni ham
--single-transactionInnoDB uchun jadvalni bloklamasdan
--quickKatta jadvalni satrma-satr o'qish
--complete-insertUstun nomlari bilan INSERT
--where="..."Faqat mos qatorlar
Terminal
# Ishlab chiqarish uchun to'liq va xavfsiz variant
mysqldump -u root -p \
  --single-transaction --routines --events --triggers \
  --default-character-set=utf8mb4 \
  dokon > dokon_$(date +%F).sql
--single-transaction nima uchun muhim?

Usiz mysqldump jadvallarni bloklaydi - sayt zaxira olinayotgan paytda "muzlab qoladi".

Bu bayroq InnoDB uchun bir zumda suratga oluvchi tranzaksiya ochadi: sayt ishlashda davom etadi, zaxira esa bir vaqt kesimidagi izchil holatni oladi.

Tiklash #

Terminal
# Bazani avval yaratish kerak
mysql -u root -p -e "CREATE DATABASE dokon CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"

# Tiklash
mysql -u root -p dokon < dokon_zaxira.sql

# --databases bilan olingan zaxirada CREATE DATABASE bor
mysql -u root -p < ikkala.sql

MySQL ichidan:

SQL
USE dokon;
SOURCE C:/zaxira/dokon_zaxira.sql;
Tiklashni sinab ko'ring

Tekshirilmagan zaxira - zaxira emas.

Har oyda bir marta test serverga tiklab ko'ring:

Terminal
mysql -u root -p -e "CREATE DATABASE dokon_test;"
mysql -u root -p dokon_test < dokon_zaxira.sql
mysql -u root -p dokon_test -e "SELECT COUNT(*) FROM buyurtmalar;"

Ko'p kompaniya zaxirasi buzuq ekanini faqat falokat kunida bilib qolgan.

Avtomatlashtirish #

3-2-1 zaxira qoidasi 3 nusxa Kamida uchta nusxa saqlang 2 joy Ikki xil turdagi saqlash qurilmasi 1 tashqarida Bittasi boshqa jismoniy joyda Namunaviy jadval Kunlik - 7 kun saqlanadi · Haftalik - 4 hafta · Oylik - 12 oy
Yong'in, o'g'irlik va inson xatosidan bir vaqtda himoyalanish

Windows uchun oddiy skript:

BAT
@echo off
set SANA=%date:~-4%-%date:~3,2%-%date:~0,2%
set YOL=C:\zaxira

C:\xampp\mysql\bin\mysqldump.exe -u root --single-transaction ^
  --routines --default-character-set=utf8mb4 ^
  dokon > %YOL%\dokon_%SANA%.sql

echo Zaxira tayyor: %YOL%\dokon_%SANA%.sql

Task Scheduler orqali har kuni ishga tushiring.

Linux uchun:

Terminal
#!/bin/bash
YOL=/var/backups/mysql
SANA=$(date +%F)

mysqldump --single-transaction --routines dokon | gzip > "$YOL/dokon_$SANA.sql.gz"

# 7 kundan eski fayllarni o'chirish
find "$YOL" -name "dokon_*.sql.gz" -mtime +7 -delete
Terminal
# crontab -e - har kuni soat 02:00 da
0 2 * * * /usr/local/bin/zaxira.sh

Migratsiyalar #

Baza tuzilmasi ham kod kabi versiyalanishi kerak.

SQL
-- database/migrations/001_mahsulotlar.sql
CREATE TABLE mahsulotlar (
    id         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    nomi       VARCHAR(150) NOT NULL,
    narx       DECIMAL(12,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- database/migrations/002_mahsulot_rasm.sql
ALTER TABLE mahsulotlar ADD COLUMN rasm VARCHAR(255) NULL AFTER nomi;
SQL
-- Qaysi migratsiya bajarilganini yozib boramiz
CREATE TABLE migratsiyalar (
    id         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    fayl       VARCHAR(255) NOT NULL UNIQUE,
    bajarilgan TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
PHP
<?php
// tools/migrate.php
$pdo = yangi_ulanish();

$bajarilgan = $pdo->query('SELECT fayl FROM migratsiyalar')
                  ->fetchAll(PDO::FETCH_COLUMN);

$fayllar = glob(__DIR__ . '/../database/migrations/*.sql');
sort($fayllar);

foreach ($fayllar as $yol) {
    $nom = basename($yol);

    if (in_array($nom, $bajarilgan, true)) {
        continue;
    }

    echo "Bajarilmoqda: {$nom}\n";
    $pdo->exec(file_get_contents($yol));

    $stmt = $pdo->prepare('INSERT INTO migratsiyalar (fayl) VALUES (?)');
    $stmt->execute([$nom]);
}

echo "Tayyor.\n";
Migratsiya fayllarini o'zgartirmang

Bir marta ishga tushgan migratsiya o'zgarmas bo'lishi kerak. Xato bo'lsa - yangi migratsiya yozing:

SQL
-- 003_narx_turini_tuzatish.sql
ALTER TABLE mahsulotlar MODIFY narx DECIMAL(12,2) NOT NULL;

PHP va PDO #

PHP
<?php
function yangi_ulanish(): PDO
{
    $host   = '127.0.0.1';
    $baza   = 'dokon';
    $kodlash = 'utf8mb4';

    $dsn = "mysql:host={$host};dbname={$baza};charset={$kodlash}";

    return new PDO($dsn, 'dokon_app', getenv('DB_PASSWORD'), [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES   => false,
    ]);
}
charset=utf8mb4 ni unutmang

DSN da kodlash ko'rsatilmasa, o'zbek harflari (o', g', sh) va emoji ? belgisiga aylanib qolishi mumkin. Bu eng ko'p uchraydigan xato.

O'qish #

PHP
$pdo = yangi_ulanish();

// Barcha qatorlar
$stmt = $pdo->query('SELECT id, nomi, narx FROM mahsulotlar ORDER BY narx DESC');
$mahsulotlar = $stmt->fetchAll();

foreach ($mahsulotlar as $mahsulot) {
    echo "{$mahsulot['nomi']} - {$mahsulot['narx']} so'm\n";
}

// Bitta qator
$stmt = $pdo->prepare('SELECT * FROM mahsulotlar WHERE id = ?');
$stmt->execute([$id]);
$mahsulot = $stmt->fetch();

if ($mahsulot === false) {
    echo "Mahsulot topilmadi.";
}

// Bitta qiymat
$stmt = $pdo->prepare('SELECT COUNT(*) FROM buyurtmalar WHERE mijoz_id = ?');
$stmt->execute([$mijozId]);
$soni = (int) $stmt->fetchColumn();

Yozish #

PHP
// Qo'shish
$stmt = $pdo->prepare(
    'INSERT INTO mahsulotlar (nomi, narx, kategoriya_id) VALUES (?, ?, ?)'
);
$stmt->execute([$nomi, $narx, $kategoriyaId]);

$yangiId = (int) $pdo->lastInsertId();

// O'zgartirish
$stmt = $pdo->prepare('UPDATE mahsulotlar SET narx = ? WHERE id = ?');
$stmt->execute([$yangiNarx, $id]);

echo $stmt->rowCount() . ' qator o\'zgardi';

// O'chirish
$stmt = $pdo->prepare('DELETE FROM mahsulotlar WHERE id = ?');
$stmt->execute([$id]);

IN bilan ishlash #

PHP
$idlar = [3, 7, 12, 18];

// Har bir qiymat uchun ? belgisi yasaymiz
$belgilar = implode(',', array_fill(0, count($idlar), '?'));

$stmt = $pdo->prepare("SELECT * FROM mahsulotlar WHERE id IN ({$belgilar})");
$stmt->execute($idlar);
$topilgan = $stmt->fetchAll();
IN (?) bitta qiymat degani
PHP
// XATO - faqat massiv "Array" satriga aylanadi
$stmt = $pdo->prepare('SELECT * FROM mahsulotlar WHERE id IN (?)');
$stmt->execute([$idlar]);

Har bir element uchun alohida ? kerak. Yuqoridagi array_fill usuli buni avtomatlashtiradi.

Massiv bo'sh bo'lsa alohida tekshiring - IN () sintaksis xatosi beradi.

Tranzaksiya #

PHP
$pdo->beginTransaction();

try {
    $stmt = $pdo->prepare(
        'INSERT INTO buyurtmalar (mijoz_id, holat) VALUES (?, ?)'
    );
    $stmt->execute([$mijozId, 'yangi']);
    $buyurtmaId = (int) $pdo->lastInsertId();

    $element = $pdo->prepare(
        'INSERT INTO buyurtma_elementlari (buyurtma_id, mahsulot_id, soni, narx)
         VALUES (?, ?, ?, ?)'
    );
    $zaxira = $pdo->prepare(
        'UPDATE mahsulotlar SET zaxira = zaxira - ? WHERE id = ? AND zaxira >= ?'
    );

    foreach ($savat as $qator) {
        $element->execute([
            $buyurtmaId, $qator['mahsulot_id'], $qator['soni'], $qator['narx'],
        ]);

        $zaxira->execute([$qator['soni'], $qator['mahsulot_id'], $qator['soni']]);

        if ($zaxira->rowCount() === 0) {
            throw new RuntimeException('Omborda yetarli mahsulot yo\'q.');
        }
    }

    $pdo->commit();
} catch (Throwable $xato) {
    $pdo->rollBack();
    error_log($xato->getMessage());
    throw $xato;
}

Ko'p qatorli qo'shish #

PHP
$qatorlar = [
    ['Klaviatura', 250000],
    ['Sichqoncha', 120000],
    ['Monitor',   1800000],
];

$stmt = $pdo->prepare('INSERT INTO mahsulotlar (nomi, narx) VALUES (?, ?)');

$pdo->beginTransaction();
foreach ($qatorlar as [$nomi, $narx]) {
    $stmt->execute([$nomi, $narx]);
}
$pdo->commit();
Tranzaksiya tezlikni oshiradi

Har bir INSERT alohida tranzaksiya bo'lsa, MySQL har safar diskka yozadi. 1000 ta qatorni bitta tranzaksiyada qo'shish 10-50 barobar tezroq.

Sahifalash #

PHP
$sahifa   = max(1, (int) ($_GET['sahifa'] ?? 1));
$chegara  = 20;
$boshlash = ($sahifa - 1) * $chegara;

$stmt = $pdo->prepare(
    'SELECT id, nomi, narx FROM mahsulotlar
     ORDER BY id DESC
     LIMIT :chegara OFFSET :boshlash'
);
$stmt->bindValue(':chegara',  $chegara,  PDO::PARAM_INT);
$stmt->bindValue(':boshlash', $boshlash, PDO::PARAM_INT);
$stmt->execute();

$mahsulotlar = $stmt->fetchAll();
$jami = (int) $pdo->query('SELECT COUNT(*) FROM mahsulotlar')->fetchColumn();
$sahifalarSoni = (int) ceil($jami / $chegara);
LIMIT uchun PARAM_INT shart

ATTR_EMULATE_PREPARES => false bo'lganda LIMIT ? ga satr uzatilsa, MySQL LIMIT '20' deb ko'radi va sintaksis xatosi beradi.

Shuning uchun bindValue(..., PDO::PARAM_INT) ishlatiladi.

Takrorlanuvchi nomlangan parametr

ATTR_EMULATE_PREPARES => false bo'lganda bir nom ikki marta ishlatilishi mumkin emas:

PHP
// XATO: Invalid parameter number
$sql = 'SELECT * FROM mahsulotlar
        WHERE nomi LIKE :qidiruv OR tavsif LIKE :qidiruv';

// TO'G'RI
$sql = 'SELECT * FROM mahsulotlar
        WHERE nomi LIKE :qidiruv1 OR tavsif LIKE :qidiruv2';
$stmt->execute(['qidiruv1' => $matn, 'qidiruv2' => $matn]);

Bu xato aynan shu saytni qurishda ham uchragan.

Xatolarni ushlash #

PHP
try {
    $stmt = $pdo->prepare('INSERT INTO mijozlar (email) VALUES (?)');
    $stmt->execute([$email]);
} catch (PDOException $xato) {
    if ((int) $xato->errorInfo[1] === 1062) {
        echo 'Bu email allaqachon ro\'yxatdan o\'tgan.';
    } else {
        error_log($xato->getMessage());
        echo 'Serverda xatolik yuz berdi.';
    }
}
KodMa'nosi
1062Takrorlanuvchi qiymat (UNIQUE buzildi)
1451Bog'liq qatorlar bor, o'chirib bo'lmaydi
1452Tashqi kalit mos kelmadi
1146Jadval mavjud emas
1054Ustun mavjud emas
1045Kirish rad etildi (login/parol)

Oddiy repozitoriy klassi #

PHP
<?php
class MahsulotRepozitoriy
{
    public function __construct(private PDO $pdo) {}

    public function toping(int $id): ?array
    {
        $stmt = $this->pdo->prepare('SELECT * FROM mahsulotlar WHERE id = ?');
        $stmt->execute([$id]);

        return $stmt->fetch() ?: null;
    }

    public function kategoriyaBoyicha(int $kategoriyaId): array
    {
        $stmt = $this->pdo->prepare(
            'SELECT id, nomi, narx FROM mahsulotlar
             WHERE kategoriya_id = ? AND faol = 1
             ORDER BY nomi'
        );
        $stmt->execute([$kategoriyaId]);

        return $stmt->fetchAll();
    }

    public function saqlang(array $malumot): int
    {
        $stmt = $this->pdo->prepare(
            'INSERT INTO mahsulotlar (nomi, narx, kategoriya_id)
             VALUES (:nomi, :narx, :kategoriya_id)'
        );
        $stmt->execute($malumot);

        return (int) $this->pdo->lastInsertId();
    }

    public function ochiring(int $id): bool
    {
        $stmt = $this->pdo->prepare('DELETE FROM mahsulotlar WHERE id = ?');
        $stmt->execute([$id]);

        return $stmt->rowCount() > 0;
    }
}
SQL ni bir joyda saqlang

So'rovlarni sahifalar bo'ylab sochib tashlamang. Har bir jadval uchun bitta repozitoriy klassi yozing - keyin so'rovni optimallashtirish kerak bo'lganda qayerga qarashni bilasiz.

Amaliy topshiriq
  1. kutubxona bazasidan mysqldump bilan zaxira oling.
  2. kutubxona_test bazasini yarating va zaxirani unga tiklang.
  3. Qatorlar sonini ikkala bazada solishtiring.
  4. --no-data bilan faqat tuzilmani saqlang va faylni o'qib ko'ring.
  5. PHP da yangi_ulanish() funksiyasini yozing.
  6. Kitoblarni sahifalab chiqaruvchi skript yozing (har sahifada 10 ta).
  7. Ijara berish amalini tranzaksiya ichida yozing: ijaralar ga qator qo'shing va kitoblar.mavjud ni kamaytiring.
  8. 1062 xatosini ushlab, foydalanuvchiga tushunarli xabar chiqaring.

Xulosa #

  • mysqldump - zaxira olishning asosiy vositasi; --single-transaction bilan sayt to'xtamaydi.
  • Zaxirani tiklab sinab ko'ring - tekshirilmagani zaxira hisoblanmaydi.
  • 3-2-1 qoidasi: 3 nusxa, 2 xil qurilma, 1 tasi boshqa joyda.
  • Tuzilma o'zgarishlarini migratsiya fayllari sifatida versiyalang.
  • PDO da ERRMODE_EXCEPTION, FETCH_ASSOC va EMULATE_PREPARES => false yoqing.
  • DSN da charset=utf8mb4 bo'lishi shart.
  • LIMIT uchun bindValue(..., PDO::PARAM_INT) ishlating.
  • Har bir nomlangan parametr so'rovda bir marta uchrasin.
  • SQL ni repozitoriy klasslariga jamlang.

Keyingi - yakuniy bo'lim: to'liq amaliy loyiha.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.