Mundarija (36)
- Bu darsda
- 1. Nega bu kerak?
- 2. MySQL oilasi
- 2.1 MySQL, MariaDB va InnoDB
- 2.2 Kanon versiyasi
- 3. Sintaksis farqlari
- 3.1 Qisqa jadval
- 3.2 Zaxira so'zlar va backtick
- 4. utf8 tuzog'i
- 4.1 Kanon chiqishi
- 4.2 Nega: 3 bayt va 4 bayt
- 4.3 Brauzerda o'zingiz tekshiring
- 5. Upsert va AUTO_INCREMENT
- 5.1 ON DUPLICATE KEY UPDATE
- 5.2 Postgres ham xuddi shunday
- 6. Vaqt: DATETIME, TIMESTAMP va 2038
- 6.1 Kanon chiqishi
- 6.2 Postgres'dagi juftlik
- 7. Node'dan: mysql2 hovuzi
- 7.1 Ulanish
- 7.2 query va execute
- 7.3 CTE va window funksiyalar
- 8. Prisma bilan ko'chish: 72 ta xato
- 8.1 Tajriba
- 8.2 Qo'lda moslash va yana ikki xato
- 8.3 Nima yo'qoldi
- 9. PlanetScale va Vitess
- 10. Ko'p uchraydigan xatolar
- 11. Mashqlar
- 1-mashq (oson): bo'shliqlarni to'ldiring
- 2-mashq (o'rta): qaysi matnlar utf8mb3 ga sig'maydi
- 3-mashq (qiyin): nomni xavfsiz qochirish
- 12. Loyiha qadami: kanon tajribasi
- 13. Real ishda
- Xulosa
- Manbalar
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:
utf8aslida 3 baytli va emoji'ni sig'dirmaydi (utf8mb4kerak),DATETIMEvaqt zonasini saqlamaydi,TIMESTAMP2038-yilda tugaydi, zaxira so'zlar backtick bilan yoziladi. Kanonvazifalar-apisxemasini 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.
utf8vautf8mb4farqini bayt darajasida tushuntirasiz va brauzerda o'zingiz tekshirasiz.- Upsert,
AUTO_INCREMENTva vaqt turlarini Postgres'dagi juftlari bilan solishtirasiz. mysql2hovuzi 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:
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):
CREATE TABLE order (id int);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):
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):
SELECT octet_length('Hisobot 𝔸 yakuni') AS bayt,
char_length('Hisobot 𝔸 yakuni') AS belgi; 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:
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:
Hisobot 𝔸 yakuni
length: 17
belgilar: 16
UTF-8 bayt: 19
true
falseUchta 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'ningCHAR_LENGTHi ham shuni sanaydi.UTF-8 bayt: 19— MySQL'ningLENGTHi va Postgres'ningoctet_lengthi.
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.utf8mb4da har belgi 4 baytgacha deb hisoblanadi. Eski MySQL'larda indeksliVARCHAR(255)shu sabab bilan xato berardi vaVARCHAR(191)(191 × 4 = 764 < 767) odatga aylandi. Prisma ham MySQL'daStringni sukut bo'yichaVARCHAR(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:
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):
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;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:
+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 1DATETIME— "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):
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;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
DATETIMEustunga 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):
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:
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):
WITH x AS (SELECT kod, soni FROM buyurtma)
SELECT kod, soni, rank() OVER (ORDER BY soni DESC) AS r
FROM x; 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:
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: 7272 ta xato. Biz ham xuddi shu sxemada takrorladik (Prisma CLI 7.10.1, bazasiz — validate faqat faylni o'qiydi) va xatolarni turlariga ajratdik:
- 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:
The schema at schema.prisma is valid 🚀
qolgan xatolar: 0Lekin 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:
=== 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_ci25 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) —
tsvectoryo'q; - RLS — MySQL'da qator darajasidagi siyosat yo'q;
- triggerlar —
tartib, tarix, teglar; CHECK/EXCLUDEcheklovlari vavazifalar_apirollari;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 validatedan o'tgan sxemadb pushda 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 CONFLICTning MySQL'dagi jufti —ON DUPLICATE KEY TIMESTAMPturi tugaydigan yil —- Kanon sxemasida
provideralmashtirilgandaprisma validatenechta xato berdi? —
Yechim
- utf8mb4 —
utf8esa aslidautf8mb3: 3 baytgacha. - backtick —
`order`; Postgres'da"order". - UPDATE —
INSERT … ON DUPLICATE KEY UPDATE soni = soni + 1. - 2038 — 19-yanvar;
DATETIMEva Postgrestimestamptzda bu chegara yo'q. - 72 — 47 tasi
Timestamptzustunlari.
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
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:
[
{ 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
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:
SELECT * FROM vazifalar ORDER BY "order"
SELECT * FROM vazifalar ORDER BY `order`
"a""b" `a``b`
Ruxsat etilmagan ustun: matn; DROP TABLE vazifalarIkki 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:
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):
Validation Error Count: 72Loyihangizning 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: "
utf8vautf8mb4farqi?", "DATETIMEvaTIMESTAMPfarqi?", "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_INCREMENTva zaxira so'zlar ro'yxatini tuzish. - Kod review'da:
CHARSET=utf8, zonasizDATETIMEga lokal vaqt yozish,executeo'rniga satr yig'ish.
Xulosa
- MySQL va MariaDB — bitta ildizdan ikki tarmoq; bugungi standart dvigatel — InnoDB.
utf8=utf8mb3, emoji va 4 baytli belgilar uchunutf8mb4kerak; PostgresUTF8da bu tuzoq yo'q.DATETIMEzonasiz,TIMESTAMPUTC'ga o'giradi va 2038 da tugaydi; Postgrestimestamptzda 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
- MySQL 8.4 hujjati — The utf8mb4 / utf8mb3 Character Set: https://dev.mysql.com/doc/refman/8.4/en/charset-unicode-utf8mb4.html
- MySQL 8.4 hujjati — The DATE, DATETIME, and TIMESTAMP Types: https://dev.mysql.com/doc/refman/8.4/en/datetime.html
- MySQL 8.4 hujjati — INSERT … ON DUPLICATE KEY UPDATE: https://dev.mysql.com/doc/refman/8.4/en/insert-on-duplicate.html
- MySQL 8.4 hujjati — AUTO_INCREMENT Handling in InnoDB: https://dev.mysql.com/doc/refman/8.4/en/innodb-auto-increment-handling.html
- MySQL 8.4 hujjati — Server SQL Modes: https://dev.mysql.com/doc/refman/8.4/en/sql-mode.html
- MariaDB hujjati — INSERT … RETURNING: https://mariadb.com/docs/server/reference/sql-statements/data-manipulation/inserting-loading-data/insertreturning (sinalmagan)
- PostgreSQL 18 hujjati — Date/Time Types: https://www.postgresql.org/docs/18/datatype-datetime.html
- mysql2: https://sidorares.github.io/node-mysql2/docs
- Prisma hujjati — MySQL connector: https://www.prisma.io/docs/orm/v7/core-concepts/supported-databases/mysql
- Vitess hujjati — History (YouTube, 2010): https://vitess.io/docs/overview/history/ (sinalmagan, 2026-11)
- PlanetScale blogi — PlanetScale for Postgres is now GA (2025-09-22): https://planetscale.com/blog/planetscale-for-postgres-is-generally-available
- Kanon o'lchovi (IlmHamroh, 2026-11-02): MySQL 8.4.13, mysql2 3.24.6, Prisma 7.10.1, Node 24.21.0. Postgres bloklari,
prisma validatetakrori va mashq yechimlari — biz ishga tushirdik (2026-11-03, PostgreSQL 17.6, Prisma CLI 7.10.1, Node 24.21.0).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!