IlmHamroh
JavaScript Full-stack/31-qism. Prodakshn backend va AI integratsiya2/34-dars21 daqiqa
Mundarija (36)

MySQL va MariaDB: Postgres'chi uchun farqlar va tuzoqlar

Qisqacha: MySQL — dunyodagi eng mashhur ochiq manbali relatsion bazalardan biri, MariaDB — uning 2009-yilda ajralgan tarmog'i. SQL asoslari Postgres bilan bir xil, lekin mayda farqlar ishlab turgan kodni buzadi: utf8 aslida 3 baytli va emoji'ni sig'dirmaydi (utf8mb4 kerak), DATETIME vaqt zonasini saqlamaydi, TIMESTAMP 2038-yilda tugaydi, zaxira so'zlar backtick bilan yoziladi. Kanon vazifalar-api sxemasini Prisma'da MySQL'ga o'tkazib ko'rdi: 72 ta xato, qo'lda moslashdan keyin ham RLS, triggerlar va FTS yo'qoldi.

Bu darsda

  • MySQL, MariaDB va InnoDB nima ekanini va ular Postgres'dan qayerda farq qilishini bilasiz.
  • utf8 va utf8mb4 farqini bayt darajasida tushuntirasiz va brauzerda o'zingiz tekshirasiz.
  • Upsert, AUTO_INCREMENT va vaqt turlarini Postgres'dagi juftlari bilan solishtirasiz.
  • mysql2 hovuzi bilan ulanish kodini o'qiy olasiz.
  • "Prisma bazani mavhumlashtiradi" degan gap qayergacha rost ekanini o'lchov bilan ko'rasiz.

Oldin bilishingiz kerak: SQLite, postgres.js va boshqa driverlar, UPSERT va MERGE, SQL'da sana va vaqt zonalari, Prisma 7: o'rnatish va arxitektura.

Oldingi darsda serversiz SQLite'ni ko'rdik. Endi Postgres'ning eng katta raqibiga o'tamiz. Bu dars sizni MySQL mutaxassisi qilmaydi. Maqsad boshqa: Postgres'ni bilgan holda MySQL loyihasiga tushsangiz, birinchi haftada qaysi tuzoqlarga tushishingizni oldindan bilish.

1. Nega bu kerak?

Ishga kirdingiz deylik. Kompaniyaning eski saytlari PHP va Laravel'da, bazasi — MySQL. WordPress, ko'p onlayn do'konlar, buxgalteriya tizimlari ham MySQL'da ishlaydi. O'zbekistondagi ko'p hosting xizmatlari ham "bitta MySQL baza" bilan keladi.

Siz SQL'ni bilasiz: SELECT, JOIN, indeks, tranzaksiya. Bularning hammasi MySQL'da ham bor. Xavf boshqa joyda — deyarli bir xil ko'rinadigan narsalarda. Kod ishlaydi, testlar o'tadi, keyin bir kun foydalanuvchi ismiga emoji qo'yadi va so'rov yiqiladi.

O'xshatish: o'zbek tilini bilgan odam qozoq tilida gaplashsa, ko'p narsani tushunadi. Lekin ba'zi so'zlar bir xil eshitilib, boshqa ma'noni beradi. Eng xavflisi — aynan shu "tanish" so'zlar.

2. MySQL oilasi

2.1 MySQL, MariaDB va InnoDB

MySQL 1995-yilda paydo bo'lgan. 2008-yilda uni Sun Microsystems sotib oldi, 2010-yilda esa Sun bilan birga Oracle'ga o'tdi. Shundan xavotirlangan MySQL asoschisi Michael Widenius loyihaning ochiq tarmog'ini (fork — mavjud kod nusxasidan boshlangan alohida loyiha) boshladi — MariaDB. Uzoq vaqt MariaDB MySQL o'rniga to'g'ridan qo'yiladigan nusxa edi. Hozir ular ancha ajralgan: ba'zi imkoniyatlar faqat birida bor.

flowchart LR
  M["MySQL (1995)"] --> O["Oracle MySQL 8.4"]
  M --> MD["MariaDB (2009)"]
  O --> V["Vitess: MySQL ustida bo'laklash"]
  V --> PS["PlanetScale (bulut)"]

Diagramma oila daraxti: bitta ildizdan ikki tarmoq. O'ng tomondagi Vitess va PlanetScale — MySQL ustidagi qatlamlar, ular haqida darsning oxirida.

MySQL'ning o'ziga xos g'oyasi — saqlash dvigateli (storage engine): jadval ma'lumotini diskda qanday saqlash va qulflashni hal qiladigan qism. Har jadval o'z dvigatelini tanlashi mumkin. Bugun deyarli hamma joyda InnoDB ishlaydi: tranzaksiyalar, qator qulflari, MVCC va tashqi kalitlar shu dvigatelda. Eski MyISAM'da tranzaksiya yo'q edi — eski loyihalarda uchrasangiz, ehtiyot bo'ling.

InnoDB'ning bitta xususiyatini bilib qo'ying: jadval qatorlari diskda birlamchi kalit tartibida joylashadi (clustered index). Shuning uchun tasodifiy UUID birlamchi kalit InnoDB'da Postgres'dagidan ham qimmatroq — har yangi qator jadvalning o'rtasiga tushadi (Primary key tanlovi darsidagi muhokama shu yerda yanada muhim).

2.2 Kanon versiyasi

Kanon MySQL 8.4.13 da sinadi (mysql:8.4 Docker image, faqat lokal port). 8.4 — MySQL'ning uzoq qo'llab-quvvatlanadigan (LTS) versiyasi. MariaDB kursda sinalmagan: undagi farqlar rasmiy hujjat bo'yicha beriladi.

Kanon ulanib, server versiyasi va sql_mode ni so'radi:

text
server: {
  v: '8.4.13',
  m: 'IGNORE_SPACE,ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'
}

sql_mode — MySQL'ning "qanchalik qattiqqo'l bo'lay" sozlamasi. Eng muhimi — STRICT_TRANS_TABLES: noto'g'ri qiymat xato beradi. Bu rejim bo'lmasa, MySQL hujjatiga ko'ra ko'p xatolar faqat ogohlantirish bo'lib, qiymat jim qirqiladi yoki o'zgartiriladi. Eski serverlarda bu rejim o'chiq bo'lishi mumkin — yangi ishda birinchi tekshiradigan narsangiz.

3. Sintaksis farqlari

3.1 Qisqa jadval

Narsa PostgreSQL MySQL
Nomni qo'shtirnoqqa olish "order" `order`
Avtomatik raqam serial, GENERATED … AS IDENTITY AUTO_INCREMENT
Upsert ON CONFLICT … DO UPDATE ON DUPLICATE KEY UPDATE
Qo'shilgan qatorni qaytarish RETURNING yo'q (MariaDB'da RETURNING bor)
Vaqt zonali tur timestamptz yo'q; TIMESTAMP sessiya zonasiga o'giradi
Mantiqiy tur boolean TINYINT(1)

Har qatorni quyida haqiqiy chiqish bilan ko'ramiz. RETURNING qatori — MySQL hujjati bo'yicha: MySQL'da qo'shilgan id'ni olish uchun drayver qaytaradigan insertId ishlatiladi.

3.2 Zaxira so'zlar va backtick

order — SQL'ning zaxira so'zi (ORDER BY). Uni jadval nomi qilsangiz, ikkala baza ham rad etadi. Postgres'da (biz ishga tushirdik, PostgreSQL 17.6):

sql
CREATE TABLE order (id int);
text
ERROR:  syntax error at or near "order"
LINE 1: CREATE TABLE order (id int);
                     ^

MySQL'da kanon xuddi shu buyruqdan ER_PARSE_ERROR oldi. Farq — qochirish usulida. Postgres'da nom qo'shtirnoqqa olinadi: "order". MySQL'da — teskari apostrof (backtick): `order`. MySQL'da qo'shtirnoq sukut bo'yicha satr belgisidir. Shuning uchun Postgres uchun yozilgan "order" MySQL'da nom emas, matn bo'lib qoladi.

Eng yaxshi yo'l — zaxira so'zni nom qilmaslik: buyurtmalar, tartib. vazifalar-api da ham tartib ustuni bor.

Tekshirib ko'ring: Postgres'ni bilgan dasturchi MySQL'da SELECT * FROM vazifalar WHERE holat = "yangi" yozdi. Bu ishlaydimi?

Javob

MySQL'ning sukut sozlamasida ishlaydi: qo'shtirnoq satrni bildiradi va "yangi" — matn. Postgres'da esa xato: "yangi" ustun nomi deb o'qiladi va column "yangi" does not exist chiqadi. Ya'ni bir xil so'rov ikki bazada boshqacha ma'no beradi. Ikkalasida ham bir xil ishlashi uchun satrni oddiy apostrof bilan yozing: 'yangi'.

4. utf8 tuzog'i

4.1 Kanon chiqishi

Bu — Postgres'chilarning MySQL'dagi eng mashhur xatosi. Kanon ikkita jadval yaratdi: biri CHARACTER SET utf8, ikkinchisi utf8mb4. Ikkalasiga ham Hisobot 𝔸 yakuni matnini yozdi (𝔸 — matematik "A" belgisi):

text
t_mb3: XATO ER_TRUNCATED_WRONG_VALUE_FOR_FIELD — Incorrect string value: '\xF0\x9D\x94\xB8 y...' for column 'matn' at row 1
t_mb4: yozildi → { matn: 'Hisobot 𝔸 yakuni', bayt: 19, belgi: 16 }

Birinchi qator: "matn ustuni uchun noto'g'ri satr qiymati: \xF0\x9D\x94\xB8". Bu to'rt bayt — aynan 𝔸 belgisi. Ikkinchi jadval esa uni bemalol saqladi: 19 bayt, 16 belgi.

4.2 Nega: 3 bayt va 4 bayt

UTF-8 da har belgi 1 dan 4 baytgacha oladi. Lotin harflari — 1 bayt, kirill — 2, ko'p Osiyo yozuvlari — 3, emoji va ba'zi maxsus belgilar — 4 bayt.

MySQL'dagi utf8 nomi tarixiy xato: u aslida utf8mb3 — har belgiga ko'pi bilan 3 bayt. Emoji unga sig'maydi. To'liq UTF-8 — utf8mb4 (most bytes 4). MySQL 8 dan beri sukut charset — utf8mb4, utf8 nomi esa eskirgan. Lekin eski jadvallar va eski darsliklardagi CHARSET=utf8 hamon uchraydi.

Postgres'da bunday tuzoq yo'q: UTF8 kodlashi to'liq 4 baytli. Xuddi shu satr (biz ishga tushirdik):

sql
SELECT octet_length('Hisobot 𝔸 yakuni') AS bayt,
       char_length('Hisobot 𝔸 yakuni') AS belgi;
text
 bayt | belgi
------+-------
   19 |    16
(1 row)

19 va 16 — MySQL'ning utf8mb4 jadvalidagi bilan bir xil.

4.3 Brauzerda o'zingiz tekshiring

Belgi qancha bayt olishini JavaScript'da ham hisoblash mumkin. TextEncoder satrni UTF-8 baytlarga aylantiradi:

js
const matn = "Hisobot \u{1D538} yakuni";
const baytlar = new TextEncoder().encode(matn).length;
console.log(matn);
console.log("length:", matn.length);
console.log("belgilar:", [...matn].length);
console.log("UTF-8 bayt:", baytlar);

function utf8mb3gaSigadimi(s) {
  for (const belgi of s) {
    if (belgi.codePointAt(0) > 0xffff) return false;
  }
  return true;
}
console.log(utf8mb3gaSigadimi("Toshkent, o'zbek tili"));
console.log(utf8mb3gaSigadimi(matn));

Konsolda:

text
Hisobot 𝔸 yakuni
length: 17
belgilar: 16
UTF-8 bayt: 19
true
false

Uchta raqamga qarang:

  • length: 17 — JavaScript satr uzunligini UTF-16 birliklarida sanaydi. 𝔸 ikkita birlik oladi (Unicode va emoji darsidagi surrogat juftlik).
  • belgilar: 16 — [...matn] satrni haqiqiy belgilarga bo'ladi. MySQL'ning CHAR_LENGTH i ham shuni sanaydi.
  • UTF-8 bayt: 19 — MySQL'ning LENGTH i va Postgres'ning octet_length i.

utf8mb3gaSigadimi qoidasi oddiy: belgi kodi 0xFFFF dan katta bo'lsa — u 4 baytli va utf8mb3 ga sig'maydi.

Diqqat: VARCHAR(100) MySQL'da 100 belgi, lekin indeks uchun bayt chegarasi bor. utf8mb4 da har belgi 4 baytgacha deb hisoblanadi. Eski MySQL'larda indeksli VARCHAR(255) shu sabab bilan xato berardi va VARCHAR(191) (191 × 4 = 764 < 767) odatga aylandi. Prisma ham MySQL'da String ni sukut bo'yicha VARCHAR(191) qiladi — buni quyida ko'ramiz.

5. Upsert va AUTO_INCREMENT

5.1 ON DUPLICATE KEY UPDATE

MySQL'da upsert — INSERT … ON DUPLICATE KEY UPDATE. Kanon order jadvaliga A-1 kodini uch marta "qo'sh yoki sonini oshir" bilan yozdi, keyin yangi B-2 qo'shdi:

text
upsert ×3 + yangi: [ { id: 1, kod: 'A-1', soni: 3 }, { id: 4, kod: 'B-2', soni: 1 } ] (B-2 id = 2 emas, 4 — har urinish AUTO_INCREMENT'ni oshirdi: innodb_autoinc_lock_mode=2)

A-1 bitta qator bo'lib qoldi, soni — 3. Lekin B-2 ning id'si 2 emas, 4. Har upsert urinishi AUTO_INCREMENT dan bitta raqam "yedi", garchi yangi qator qo'shilmagan bo'lsa ham.

5.2 Postgres ham xuddi shunday

Bu MySQL'ga xos kamchilik deb o'ylash oson. Lekin Postgres'da xuddi shu tajribani qilaylik (UPSERT va MERGE darsidagi ON CONFLICT):

sql
CREATE TABLE buyurtma (
  id serial PRIMARY KEY,
  kod text UNIQUE,
  soni int NOT NULL DEFAULT 1
);
INSERT INTO buyurtma (kod) VALUES ('A-1')
  ON CONFLICT (kod) DO UPDATE SET soni = buyurtma.soni + 1;
INSERT INTO buyurtma (kod) VALUES ('A-1')
  ON CONFLICT (kod) DO UPDATE SET soni = buyurtma.soni + 1;
INSERT INTO buyurtma (kod) VALUES ('A-1')
  ON CONFLICT (kod) DO UPDATE SET soni = buyurtma.soni + 1;
INSERT INTO buyurtma (kod) VALUES ('B-2');
SELECT id, kod, soni FROM buyurtma ORDER BY id;
text
CREATE TABLE
INSERT 0 1
INSERT 0 1
INSERT 0 1
INSERT 0 1
 id | kod | soni
----+-----+------
  1 | A-1 |    3
  4 | B-2 |    1
(2 rows)

Natija aynan bir xil: B-2 ning id'si 4. Postgres ham nextval() ni ziddiyat tekshiruvidan oldin chaqiradi va raqam qaytmaydi. Tranzaksiya asoslari darsida ko'rgandik: sequence ROLLBACK da ham qaytmaydi.

Saboq ikkala baza uchun bitta: id'lar ketma-ket bo'lishiga tayanmang. "Buyurtma №4" dan oldin 3 ta buyurtma bo'lgan degan xulosa — xato. Uzluksiz raqam kerak bo'lsa (masalan, hisob-faktura raqami), uni alohida jadval va qulf bilan yasaysiz.

6. Vaqt: DATETIME, TIMESTAMP va 2038

6.1 Kanon chiqishi

MySQL'da vaqt uchun ikki tur bor. Kanon sessiya zonasini +05:00 (Toshkent) qilib, ikkalasiga 2026-11-02 10:00 yozdi. Keyin sessiyani UTC ga o'tkazib o'qidi:

text
+05:00 da yozilgan 10:00, UTC sessiyada: [ { dt: '2026-11-02 10:00:00.000', ts: '2026-11-02 05:00:00.000' } ]
TIMESTAMP 2040: ER_TRUNCATED_WRONG_VALUE — Incorrect datetime value: '2040-01-01 00:00:00' for column 'ts' at row 1
  • DATETIME — "devordagi soat ko'rsatkichi". Zona saqlanmaydi: 10:00 yozildi, kim qayerdan o'qisa ham 10:00.
  • TIMESTAMP — vaqt UTC'ga o'girib saqlanadi va o'qishda sessiya zonasiga qaytariladi. Toshkentdagi 10:00 = UTC 05:00. Lekin u 4 baytli soniyalar soniga asoslangan va hujjatga ko'ra 2038-yil 19-yanvar bilan tugaydi. 2040-yilni u umuman qabul qilmadi.

2038 — uzoq emas. Bugun yozilayotgan 15 yillik kredit yoki obuna muddati allaqachon bu chegaradan o'tadi.

6.2 Postgres'dagi juftlik

Postgres'da ham ikki tur bor va ular xuddi shunday farq qiladi (SQL'da sana va vaqt zonalari darsi):

sql
SET TIME ZONE 'Asia/Tashkent';
CREATE TABLE vaqt_sinov (oddiy timestamp, zonali timestamptz);
INSERT INTO vaqt_sinov VALUES
  ('2026-11-02 10:00:00', '2026-11-02 10:00:00'),
  ('2040-01-01 00:00:00', '2040-01-01 00:00:00');
SET TIME ZONE 'UTC';
SELECT oddiy, zonali FROM vaqt_sinov;
text
SET
CREATE TABLE
INSERT 0 2
SET
        oddiy        |         zonali
---------------------+------------------------
 2026-11-02 10:00:00 | 2026-11-02 05:00:00+00
 2040-01-01 00:00:00 | 2039-12-31 19:00:00+00
(2 rows)

timestamp = MySQL DATETIME (zonasiz), timestamptz = MySQL TIMESTAMP (UTC'ga o'giriladi). Farq — chegarada: timestamptz 2040-yilni bemalol saqladi. Uning oralig'i Postgres hujjatiga ko'ra 294 276-yilgacha.

Shuning uchun Postgres'dan MySQL'ga ko'chishda har bir timestamptz ustuni uchun qaror kerak: TIMESTAMP (zona bor, 2038 chegarasi) yoki DATETIME (chegara yo'q, zona yo'qoladi va ilova uni hamma joyda UTC deb yozishi shart).

Tekshirib ko'ring: MySQL'dagi DATETIME ustunga Toshkentdagi server 10:00 yozdi. Keyin baza Frankfurtdagi serverga ko'chirildi, u yerda sessiya zonasi UTC. O'qilganda qancha ko'rinadi va bu to'g'rimi?

Javob

10:00 ko'rinadi — DATETIME zonani bilmaydi va hech narsani o'girmaydi. Agar ilova "bazadagi hamma vaqt UTC" deb hisoblasa, endi u 10:00 ni UTC deb o'qiydi, ya'ni Toshkent vaqti bilan 15:00. Besh soatlik xato. Shuning uchun DATETIME bilan ishlaganda ilova doim UTC yozishi kerak — kanon mysql2 hovuzida timezone: "Z" qo'ygani shuning uchun.

7. Node'dan: mysql2 hovuzi

7.1 Ulanish

Node'da MySQL uchun asosiy drayver — mysql2 (kanonda 3.24.6). Kanon kodi (qatorlar telefon uchun bo'lingan, ma'nosi aynan):

js
import mysql from "mysql2/promise";

const pool = mysql.createPool({
  host: "127.0.0.1",
  port: 3317,
  user: "root",
  password: process.env.MYSQL_PAROL,
  database: "vazifalar",
  connectionLimit: 5,
  timezone: "Z",
});
const q = async (sql, p) => (await pool.query(sql, p))[0];

await q("INSERT INTO t_mb4 VALUES (?)", ["Hisobot"]);
console.log(await q("SELECT VERSION() v, @@sql_mode m"));
await pool.end();

Kanonda parol lokal konteyner uchun skriptga yozilgan edi. Bu yerda uni muhit o'zgaruvchisidan olamiz — haqiqiy loyihada parol kodda turmaydi. Postgres'dagi pg bilan farqlar:

pg mysql2
Parametr belgisi $1, $2 ?
Hovuz new pg.Pool({ max }) createPool({ connectionLimit })
Natija res.rows [rows, fields] massiv
Qo'shilgan id RETURNING id result.insertId

7.2 query va execute

mysql2 da so'rov yuborishning ikki usuli bor. pool.query(sql, params) — parametrlarni drayver o'zi ekranlab, tayyor SQL matnini yuboradi. pool.execute(sql, params) — serverda tayyorlangan so'rov (prepared statement) yaratadi va parametrlarni alohida yuboradi. Ikkalasi ham SQL injection'dan himoya qiladi, agar ? ishlatilsa (Parametrli so'rovlar darsi). Hech qachon satrni + yoki shablon satr bilan yig'mang.

7.3 CTE va window funksiyalar

MySQL 8 gacha CTE va window funksiyalar yo'q edi — eski maqolalarda "MySQL'da WITH yo'q" degan gapni uchratasiz. 8-versiyadan boshlab ular bor. Kanon chiqishi:

text
window: [ { kod: 'A-1', soni: 3, r: 1 }, { kod: 'B-2', soni: 1, r: 2 } ]

Postgres'da xuddi shu so'rov (biz ishga tushirdik, oldingi buyurtma jadvalida):

sql
WITH x AS (SELECT kod, soni FROM buyurtma)
SELECT kod, soni, rank() OVER (ORDER BY soni DESC) AS r
FROM x;
text
 kod | soni | r
-----+------+---
 A-1 |    3 | 1
 B-2 |    1 | 2
(2 rows)

Bir xil natija. Window funksiyalar bilimingiz MySQL 8 da to'liq ishlaydi.

8. Prisma bilan ko'chish: 72 ta xato

8.1 Tajriba

Prisma'ning va'dasi: "sxemani bir marta yozasiz, provayderni almashtirib, boshqa bazaga ulanasiz". Kanon buni sinab ko'rdi: vazifalar-api ning schema.prisma faylida faqat bitta qatorni o'zgartirdi — provider = "postgresql" → "mysql". Keyin prisma validate:

text
Error: Prisma schema validation - (validate wasm)
Error code: P1012
error: Native type Timestamptz is not supported for mysql connector.
  -->  schema.prisma:24
...
Validation Error Count: 72

72 ta xato. Biz ham xuddi shu sxemada takrorladik (Prisma CLI 7.10.1, bazasiz — validate faqat faylni o'qiydi) va xatolarni turlariga ajratdik:

provider = "mysql": prisma validate xatolari
  • Timestamptz47 ta
  • Partial indeks (WHERE)9 ta
  • PK nomi (map)6 ta
  • Inet (IP manzil)4 ta
  • Massiv (String[])3 ta
  • Indeks turi (GIN)2 ta
  • Uuid1 ta

Manba: IlmHamroh kanon o'lchovi (2026-11-02) va o'z takrorimiz (2026-11-03): vazifalar-api schema.prisma, Prisma CLI 7.10.1, jami 72

Grafikdagi eng uzun ustun — Timestamptz: 47 ta ustun. MySQL'da zonali vaqt turi yo'q (oldingi bo'lim). Qolganlari — Postgres'ning o'z imkoniyatlari: partial indeks, inet, massiv, GIN.

8.2 Qo'lda moslash va yana ikki xato

Kanon har xatoni qoidalar bilan almashtirdi: Timestamptz → DateTime(6) (zona yo'qoladi), inet → VarChar(45), massiv → Json, partial indeks → oddiy indeks, GIN va tsvector — olib tashlandi, @dbsiz har String → VarChar(191), Bytes → VarBinary(255). Validate o'tdi:

text
  The schema at schema.prisma is valid 🚀
  qolgan xatolar: 0

Lekin bazaga yozishda (db push) sxema yana yiqildi. Kanon skriptidagi izohga ko'ra, birinchi urinishda token_hash ustuni TEXT bo'lib qolgan va MySQL "BLOB/TEXT column used in key specification without a key length" — "uzunliksiz TEXT ustunga indeks qo'yib bo'lmaydi" degan. Ya'ni validate dan o'tgan sxema bazada baribir yiqilishi mumkin. Oxirgi moslashdan keyin:

text
=== 3) db push (MySQL 8.4)
  Your database is now in sync with your Prisma schema. Done in 10.29s
=== 4) jadvallar va charset
  25
  utf8mb4	utf8mb4_0900_ai_ci

25 jadval, utf8mb4.

8.3 Nima yo'qoldi

Jadvallar paydo bo'ldi, lekin 26–28-qismlarda qurgan eng qimmatli narsalar ko'chmadi:

  • o'zbekcha to'liq matnli qidiruv (FTS) — tsvector yo'q;
  • RLS — MySQL'da qator darajasidagi siyosat yo'q;
  • triggerlar — tartib, tarix, teglar;
  • CHECK/EXCLUDE cheklovlari va vazifalar_api rollari;
  • timestamptz — 47 ustunda vaqt zonasi.

Xulosa: "Prisma bazani mavhumlashtiradi" — faqat oddiy CRUD qismi uchun rost. Postgres'ning kuchli tomonlarini ishlatgan loyiha provayderni bitta qator bilan almashtira olmaydi. Bu xato emas, tanlov: biz Postgres'ni aynan shu imkoniyatlar uchun tanlaganmiz.

Tekshirib ko'ring: Nega prisma validate dan o'tgan sxema db push da yiqildi?

Javob

validate faqat Prisma qoidalarini tekshiradi: "bu tur shu provayderda bormi, atributlar to'g'rimi". U bazaning o'z cheklovlarini bilmaydi. MySQL'ning "TEXT ustunga uzunliksiz indeks qo'yib bo'lmaydi" qoidasi faqat haqiqiy CREATE INDEX paytida chiqdi. Shuning uchun ko'chishni doim haqiqiy bazada sinash kerak.

9. PlanetScale va Vitess

MySQL'ni gorizontal kengaytirishning eng mashhur yo'li — Vitess: MySQL serverlari ustidagi qatlam. U ma'lumotni ko'p serverga bo'lib saqlaydi — buni sharding (bo'laklash) deyishadi. Vitess hujjatiga ko'ra, uni YouTube jamoasi 2010-yilda MySQL'ni masshtablash uchun yaratgan, hozir u ochiq manbali loyiha. PlanetScale — Vitess asosidagi bulut xizmati: sxema o'zgarishlarini "tarmoq" (branch) qilib sinash va jadvalni qulflamasdan o'zgartirish bilan mashhur. 2025-yil sentyabridan PlanetScale Postgres xizmatini ham taklif qiladi (kompaniya blogi bo'yicha).

Ikkalasi ham kursda sinalmagan (hisob va klaster kerak) — rasmiy hujjat bo'yicha, 2026-11. Bo'laklash g'oyasining o'zini Postgres misolida Sharding va taqsimlangan SQL darsida ko'ramiz.

10. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Incorrect string value: '\xF0…' ustun utf8 (= utf8mb3) utf8mb4
ER_PARSE_ERROR jadval nomida zaxira so'z boshqa nom yoki backtick
Server ko'chgach vaqtlar 5 soat siljidi DATETIME zonasiz, ilova UTC yozmagan ilova doim UTC yozsin
2038 dan keyingi sana rad etildi TIMESTAMP chegarasi DATETIME + UTC
Noto'g'ri qiymat jim qirqildi STRICT_TRANS_TABLES o'chiq sql_mode ni tekshiring
"id'lar orasida teshik" upsert va ROLLBACK raqam yeydi ketma-ketlikka tayanmang

11. Mashqlar

Mashqlar kurs/mashqlar/31/02-mysql/ da. 2- va 3-mashq yechimlari Node 24.21.0 da tekshirilgan (biz ishga tushirdik).

1-mashq (oson): bo'shliqlarni to'ldiring

  • MySQL'dagi to'liq, 4 baytli UTF-8 charset —
  • MySQL'da zaxira so'zni nom qilish uchun ishlatiladigan belgi —
  • Postgres'dagi ON CONFLICT ning MySQL'dagi jufti — ON DUPLICATE KEY
  • TIMESTAMP turi tugaydigan yil —
  • Kanon sxemasida provider almashtirilganda prisma validate nechta xato berdi? —
Yechim
  • utf8mb4 — utf8 esa aslida utf8mb3: 3 baytgacha.
  • backtick — `order`; Postgres'da "order".
  • UPDATE — INSERT … ON DUPLICATE KEY UPDATE soni = soni + 1.
  • 2038 — 19-yanvar; DATETIME va Postgres timestamptz da bu chegara yo'q.
  • 72 — 47 tasi Timestamptz ustunlari.

2-mashq (o'rta): qaysi matnlar utf8mb3 ga sig'maydi

Eski MySQL bazasini utf8mb4 ga o'tkazishdan oldin, qaysi yozuvlar muammo berishini bilish kerak. muammolilar(matnlar) funksiyasini yozing: u utf8mb3 ga sig'maydigan matnlarni topsin va har biri uchun { matn, belgi } qaytarsin (belgi — birinchi 4 baytli belgining kodi, U+1D538 shaklida). Ishora: for…of satrni belgilarga bo'ladi, codePointAt(0), toString(16).toUpperCase() (sanoq tizimlari).

Yechim
js
function birinchiTortBaytli(s) {
  for (const belgi of s) {
    const kod = belgi.codePointAt(0);
    if (kod > 0xffff) return kod;
  }
  return null;
}

function muammolilar(matnlar) {
  const natija = [];
  for (const matn of matnlar) {
    const kod = birinchiTortBaytli(matn);
    if (kod === null) continue;
    const belgi = "U+" + kod.toString(16).toUpperCase();
    natija.push({ matn, belgi });
  }
  return natija;
}

const vazifalar = [
  "Non olish",
  "Hisobot \u{1D538} yakuni",
  "Привет, Toshkent",
  "Tug'ilgan kun \u{1F382}",
];
console.log(muammolilar(vazifalar));

Konsolda:

text
[
  { matn: 'Hisobot 𝔸 yakuni', belgi: 'U+1D538' },
  { matn: "Tug'ilgan kun 🎂", belgi: 'U+1F382' }
]

Ikkinchi matn qo'shtirnoqda chiqdi — ichida apostrof bor, Node shunda boshqa qo'shtirnoq tanlaydi. Kirill matni ro'yxatga tushmadi — kirill harflari 2 bayt va utf8mb3 ga sig'adi. Faqat 4 baytli belgilar (matematik harf va tort emoji'si) muammo. Haqiqiy bazada xuddi shu tekshiruvni SQL bilan qilish mumkin: LENGTH(matn) <> CHAR_LENGTH(matn) ko'p baytli qatorlarni topadi, lekin 2 va 4 baytlini ajratmaydi — shuning uchun ilova tomonidagi tekshiruv aniqroq.

3-mashq (qiyin): nomni xavfsiz qochirish

Ba'zan jadval yoki ustun nomi o'zgaruvchidan keladi (masalan, foydalanuvchi tanlagan saralash ustuni). Nomni parametr (?, $1) bilan berib bo'lmaydi — parametr faqat qiymat uchun. Ikki funksiya yozing: pgNomi(nom) — Postgres uchun "…" ga oladi va ichidagi " ni "" ga ikkilantiradi; mysqlNomi(nom) — `…` ga oladi va ichidagi backtick'ni ikkilantiradi. Keyin ruxsat ro'yxati (allowlist) bilan ishlaydigan saralash(ustun, baza) yozing: ustun ["matn", "muddat", "order"] dan bo'lmasa — xato tashlasin. Ishora: replaceAll (matnni o'zgartirish), includes (massivda qidirish).

Yechim
js
const pgNomi = (nom) => `"${nom.replaceAll('"', '""')}"`;
const mysqlNomi = (nom) => "`" + nom.replaceAll("`", "``") + "`";

const RUXSAT = ["matn", "muddat", "order"];

function saralash(ustun, baza) {
  if (!RUXSAT.includes(ustun)) {
    throw new Error(`Ruxsat etilmagan ustun: ${ustun}`);
  }
  const nom = baza === "mysql" ? mysqlNomi(ustun) : pgNomi(ustun);
  return `SELECT * FROM vazifalar ORDER BY ${nom}`;
}

console.log(saralash("order", "postgres"));
console.log(saralash("order", "mysql"));
console.log(pgNomi('a"b'), mysqlNomi("a`b"));
try {
  saralash("matn; DROP TABLE vazifalar", "mysql");
} catch (e) {
  console.log(e.message);
}

Konsolda:

text
SELECT * FROM vazifalar ORDER BY "order"
SELECT * FROM vazifalar ORDER BY `order`
"a""b" `a``b`
Ruxsat etilmagan ustun: matn; DROP TABLE vazifalar

Ikki qatlam himoya: ruxsat ro'yxati faqat ma'lum nomlarni o'tkazadi, qochirish esa nom ichidagi qo'shtirnoq yoki backtick so'rovdan "chiqib ketishiga" yo'l qo'ymaydi. Ikkalasi ham bo'lgani yaxshi — biri xato qilsa, ikkinchisi ushlaydi. pg da shu ish uchun tayyor escapeIdentifier, mysql2 da escapeId funksiyasi bor.

12. Loyiha qadami: kanon tajribasi

vazifalar-api Postgres'da qoladi va bu darsda kod o'zgarmaydi, commit yo'q. Sabab — tajribaning o'zi: MySQL'ga o'tish 26–28-qismlardagi FTS, RLS, triggerlar va rollarni yo'qotadi, 47 ustunda vaqt zonasi yo'qoladi.

Kanon tajribasi ikki qismdan iborat edi: mysql2 bilan tuzoqlar (haqiqiy MySQL 8.4.13 lokal konteynerda) va Prisma sxemasini ko'chirish. Ikkinchisining birinchi qadamini o'zingiz takrorlay olasiz — baza ham, Docker ham kerak emas, chunki prisma validate faqat faylni o'qiydi. Loyihangizning ildiz papkasida (vazifalar-api, package.json turgan joy), Git Bash'da:

bash
mkdir -p /tmp/mysql-sinov
sed -e 's/provider = "postgresql"/provider = "mysql"/' \
  -e '/output *=/d' prisma/schema.prisma \
  > /tmp/mysql-sinov/schema.prisma
npx prisma validate --schema /tmp/mysql-sinov/schema.prisma \
  2>&1 | grep "Error Count"

Birinchi sed provayderni almashtiradi, ikkinchisi Prisma Client chiqish papkasi qatorini olib tashlaydi (nusxa boshqa papkada). Prisma xatolarni stderr'ga yozadi — shuning uchun 2>&1; grep esa oxirgi yig'indi qatorini oladi. Biz 30-qism oxiridagi loyiha nusxasida ishga tushirdik (Prisma CLI 7.10.1):

text
Validation Error Count: 72

Loyihangizning prisma.config.ts fayli DATABASE_URL ni o'qishi mumkin — validate baribir bazaga ulanmaydi. Ish tugagach /tmp/mysql-sinov papkasini o'chiring. Loyiha fayllariga hech narsa yozilmadi.

13. Real ishda

  • Qayerda uchraydi: WordPress, Laravel va boshqa PHP loyihalari, ko'p eski korporativ tizimlar, hosting panellaridagi "bitta MySQL baza". Katta kompaniyalar MySQL'ni Vitess bilan masshtablaydi.
  • Intervyu savollari: "utf8 va utf8mb4 farqi?", "DATETIME va TIMESTAMP farqi?", "InnoDB va MyISAM?", "Postgres va MySQL'dan qaysi birini tanlaysiz va nega?"
  • Ko'chish loyihalarida: MySQL → Postgres ko'chishi ko'p uchraydi; birinchi ish — charset, vaqt turlari, AUTO_INCREMENT va zaxira so'zlar ro'yxatini tuzish.
  • Kod review'da: CHARSET=utf8, zonasiz DATETIME ga lokal vaqt yozish, execute o'rniga satr yig'ish.

Xulosa

  • MySQL va MariaDB — bitta ildizdan ikki tarmoq; bugungi standart dvigatel — InnoDB.
  • utf8 = utf8mb3, emoji va 4 baytli belgilar uchun utf8mb4 kerak; Postgres UTF8 da bu tuzoq yo'q.
  • DATETIME zonasiz, TIMESTAMP UTC'ga o'giradi va 2038 da tugaydi; Postgres timestamptz da chegara yo'q.
  • Upsert ikkala bazada ham id raqamlarini "yeydi" — ketma-ketlikka tayanmang.
  • mysql2: ? parametrlar, createPool, insertId; MySQL 8 da CTE va window funksiyalar bor.
  • Prisma provayderni almashtirish — 72 xato va Postgres imkoniyatlarining yo'qolishi: mavhumlashtirish faqat CRUD uchun.

Keyingi dars: Replikatsiya va read replica'lar — Postgres'ning nusxasini ikkinchi serverda jonli ushlab turamiz, o'qishni unga yo'naltiramiz va "yozdim, lekin ko'rinmayapti" muammosini o'lchaymiz.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
MySQL va MariaDB: Postgres'chi uchun farqlar va tuzoqlar — IlmHamroh