عندما تقوم بتثبيت تطبيق مثل WordPress أو BookStack سوف تجد في التوثيق جملة مثل «يعمل مع MySQL أو MariaDB»، وكأن الاثنين اسمان لشيء واحد، وهذا كان صحيحاً تقريباً قبل خمسة عشر عاماً، أما اليوم فهما قاعدتا بيانات مختلفتان تتشاركان الأصل والبروتوكول وأغلب لغة SQL، وتختلفان في أشياء كثيرة لن تراها حتى تنقل بياناتك من واحدة إلى الأخرى أو تكتب استعلاماً يعمل على واحدة ويفشل على الأخرى. وفي مقال أنواع قواعد البيانات تكلمنا عن القواعد العلائقية Relational بشكل عام، وهنا سوف ننزل إلى الاثنين بالتحديد ونشغلهما جنباً إلى جنب على نفس الجدول ونفس الاستعلامات حتى نرى بالضبط أين يتوقف التطبيق.
وسوف نناقش في هذا المقال ما يلي:
- كيف خرج MariaDB من MySQL، ومن يملك كل واحد منهما اليوم.
- الرخص: ما المفتوح وما المغلق وما الذي يقع بينهما.
- التوافق Compatibility اليوم: ما الذي ما زال مشتركاً وأين ينكسر.
- تجربة عملية على MySQL 9.7.2 وMariaDB 12.3.3 بمليون طلب، مع أربعة أماكن ينكسر فيها النقل.
- النسخ المتماثل Replication والعناقيد في كل منهما.
- متى تختار كل واحد، وكيف تنقل بياناتك من MySQL إلى MariaDB دون مفاجآت.
من أين جاء MariaDB؟
ظهرت أول نسخة من MySQL في 23 مايو 1995 من شركة سويدية اسمها MySQL AB، وفي يناير 2008 اشترتها Sun Microsystems بمليار دولار، ثم أعلنت Oracle شراء Sun في 2009 وأكملت الصفقة في 27 يناير 2010 كما يذكر سجل MySQL. وهنا ظهرت المشكلة التي أنتجت MariaDB، فOracle تبيع أشهر قاعدة بيانات تجارية في العالم، وأصبحت الآن تملك أشهر منافس مفتوح لها، فقام Michael Widenius الملقب Monty، وهو أحد مؤسسي MySQL الأصليين، بعمل نسخة مستقلة Fork من الكود في 2009 وسماها MariaDB على اسم ابنته الصغرى (كما سمى MySQL قبلها على اسم ابنته My)، وصدرت أول نسخة منها 5.1.38 في 29 أكتوبر 2009 بحسب سجل MariaDB، والهدف المعلن أن يبقى الكود تحت رخصة GPL دون أن يعتمد مصيره على شركة واحدة.
والصورة التالية تبين الخطين على محور زمني واحد:

وفي السنوات الأولى كان MariaDB نسخة من MySQL بأرقام الإصدار نفسها، تستطيع أن تضعه مكانها على نفس الملفات ويعمل التطبيق دون أن يشعر، ولكن منذ MariaDB 10.0 بدأت الأرقام تفترق، ومنذ MySQL 8.0 افترق شكل الملفات على القرص نفسه كما سوف نرى في التجربة، ولذلك صفحة التوافق في توثيق MariaDB تصف كل إصدار بعد MariaDB 10.4 بقائمة اختلافات، وليس بأنه بديل مباشر Drop-in Replacement.
من يملك كل واحد منهما اليوم؟
هذا السؤال مهم أكثر مما يبدو، والسبب أن من يملك الكود ويوظف أغلب المطورين هو الذي يحدد ماذا يدخل في الإصدار القادم وماذا يبقى في النسخة التجارية فقط.
- MySQL: تملكها Oracle بالكامل، وهي التي تكتب الكود وتصدر النسخة المجانية Community والنسخة التجارية Enterprise. ونموذج الإصدارات بحسب صفحة الإصدارات فيه مساران: إصدارات طويلة الدعم LTS لا تحذف فيها ميزات وتدعم ثماني سنوات (خمس دعم أساسي وثلاث ممتدة)، وإصدارات الابتكار Innovation التي تأتي بالميزات الجديدة وتغير السلوك ويدعم كل منها حتى يصدر الذي بعده. وكان آخر إصدار LTS هو 9.7 في أبريل 2026 بعد 8.4 في 2024، والصفحة نفسها تقول إن 9.7 آخر إصدار بالأرقام المتتالية، وبعده تستخدم MySQL أرقاماً بالتاريخ بالشكل YY.M مثل 26.7.
- MariaDB: هنا جهتان. الأولى مؤسسة MariaDB Foundation، وهي جهة غير ربحية مهمتها أن يبقى كود MariaDB Server مفتوحاً للاستخدام والمساهمة مستقلاً عن أي شركة. والثانية الشركة MariaDB plc التي توظف أغلب المطورين وتبيع النسخة Enterprise والدعم. هذه الشركة دخلت بورصة نيويورك في ديسمبر 2022، ثم اشترتها K1 Investment Management في سبتمبر 2024 وأخرجتها من البورصة، وبعدها اشترت Codership صاحبة Galera Cluster في مايو 2025، وأعادت شراء SkySQL في أغسطس 2025. وسياسة الإصدارات في صفحة المؤسسة أن يصدر إصدار LTS جديد كل سنة، يدعم مجاناً ثلاث سنوات بعد صدوره، وآخرها 12.3 الذي صدر في 28 مايو 2026 ويدعم حتى يونيو 2029، وقبله 11.8 حتى يونيو 2028 و11.4 حتى مايو 2029.
وقد يتساءل البعض: هل يعني تغير ملاك الشركة أن MariaDB قد تغير رخصتها كما فعلت Redis وMongoDB؟ والإجابة أن الكود الأساسي مسجل باسم كثير من المساهمين تحت GPLv2، والمؤسسة تملك الاسم والمستودع، وهذا يجعل تغيير الرخصة صعباً جداً، ولكن الأدوات الجانبية التي تكتبها الشركة وحدها قصة أخرى كما سوف نرى في القسم التالي.
الرخص: ما المفتوح وما المغلق؟
شرحنا معنى GPL وCopyleft والرخص التي تسمى مصدراً متاحاً Source Available في مقال الرخص، والمهم هنا أن تعرف ماذا يقع تحت كل رخصة:
| الجزء | MySQL | MariaDB |
|---|---|---|
| السيرفر | GPLv2 للنسخة Community كما في ملف الرخصة، ورخصة تجارية من Oracle لمن يريد أن يدمجها في منتج مغلق | GPLv2 كما في المستودع |
| النسخ الاحتياطي الساخن Hot Backup | MySQL Enterprise Backup تجاري، والبديل المفتوح من خارج Oracle | mariadb-backup داخل النسخة المجانية |
| Thread Pool والتدقيق Audit والتشفير على القرص | ضمن MySQL Enterprise التجارية | Thread Pool وAudit Plugin والتشفير في النسخة المجانية |
| البروكسي | MySQL Router تحت GPLv2 | MaxScale تحت BSL 1.1، مجاني لأقل من ثلاثة سيرفرات في الإنتاج، ويتحول إلى GPL بعد تاريخ محدد لكل إصدار |
ولاحظ أن رخصة GPL لا تمنعك من تشغيل أي منهما داخل شركتك أو خلف تطبيقك على الإنترنت، فهي تتعلق بتوزيع البرنامج نفسه، ولذلك أغلب من يستضيف تطبيقاته لن يلمس فرقاً في الرخصة، والفرق الحقيقي هو في الميزات التي تحتاجها، مثل النسخ الاحتياطي الساخن وThread Pool، وهل هي في النسخة المجانية أم في نسخة تدفع ثمنها.
التوافق اليوم: ما الذي ما زال مشتركاً؟
الكثير ما زال مشتركاً، وهذا هو سبب انتشار جملة «MySQL أو MariaDB» في التوثيق: نفس بروتوكول الاتصال على المنفذ 3306، لذلك أغلب المكتبات Drivers تتصل بالاثنين، ونفس محرك التخزين الافتراضي InnoDB، ونفس أوامر SQL الأساسية من SELECT وJOIN والمفاتيح الأجنبية Foreign Keys والمعاملات Transactions، ونفس شكل ملف my.cnf في أغلب الإعدادات، ونفس الأمر mysqldump للنسخ المنطقي. وتطبيق يكتب SQL عادياً عبر ORM لن يرى في الغالب أي فرق.
أما الاختلافات فتظهر في خمسة أماكن، وكل واحد منها سوف نراه في التجربة:
- JSON: في MySQL هو نوع حقيقي يخزن بصيغة ثنائية Binary ويقارن كقيم JSON، وفي MariaDB هو اسم آخر لـ LONGTEXT مع شرط CHECK يتحقق أن النص JSON صالح، كما في توثيق نوع JSON، والمعامل
->>المختصر غير موجود في MariaDB. - الصياغة الإضافية: كل واحد أضاف أشياء ليست عند الآخر، فMariaDB عنده INSERT … RETURNING والسلاسل Sequences، وMySQL عنده أشياء مثل المعاملات المختصرة لل JSON.
- ال GTID في النسخ المتماثل: MySQL يكتبه بالشكل
UUID:رقم، وMariaDB بالشكلDomain-ServerID-رقم، والصيغتان غير متوافقتين بحسب مصفوفة التوافق. - المصادقة Authentication: MySQL يستخدم
caching_sha2_passwordافتراضياً وعطلmysql_native_passwordافتراضياً منذ 8.4، وMariaDB ما زال يستخدمmysql_native_passwordللمستخدمين الذين ينشئهم. - الملفات على القرص: MySQL 8.0 حذف ملفات
.frmووضع تعريف الجداول في قاموس بيانات Data Dictionary داخلي، وMariaDB ما زال يحتاج.frm، ولذلك يقول دليل النقل إن الانتقال من MySQL 8 لا يكون إلا بنسخة منطقية Dump ثم استعادتها.
والصورة التالية تلخص ما خرج من التجربة:

تجربة عملية: نفس الجدول على السيرفرين
سوف نشغل MySQL 9.7.2 (إصدار LTS) وMariaDB 12.3.3 (إصدار LTS) جنباً إلى جنب، وننشئ نفس الجدول، ونحمل فيه نفس مليون طلب، ثم نشغل نفس الاستعلامات. والتجربة تعمل على Docker Engine 29.8.0 في Docker Desktop مع WSL2 على جهاز بثمانية أنوية و32 GB ذاكرة، والأرقام للتوضيح فقط وليست قياس أداء Benchmark، والسبب أن ملف البيانات يقرأ من مجلد Windows مشترك وهو أبطأ من قرص سيرفر حقيقي، والإعدادات كلها افتراضية.
ملفات التجربة
الملف الأول يولد مليون طلب ثابت في كل مرة (البذرة Seed ثابتة)، وهو نفس الملف الذي سوف نستخدمه في مقالات MongoDB وClickHouse والمقارنة حتى تكون الأرقام على نفس البيانات:
# Writes orders.csv: the same 1,000,000 orders used by the database labs of ArabRoot (fixed seed, same file every time).
# python gen_orders.py [rows]
import csv, random, sys
from datetime import datetime, timedelta
N = int(sys.argv[1]) if len(sys.argv) > 1 else 1_000_000
rnd = random.Random(42)
countries = ['SA', 'EG', 'AE', 'JO', 'MA', 'KW', 'QA', 'DZ']
statuses = ['paid'] * 7 + ['refunded', 'cancelled', 'pending']
start = datetime(2025, 1, 1)
with open('orders.csv', 'w', newline='') as f:
w = csv.writer(f)
for i in range(1, N + 1):
ts = start + timedelta(seconds=rnd.randrange(0, 365 * 86400))
w.writerow([i, rnd.randrange(1, 100_001), rnd.choice(countries), rnd.choice(statuses),
f'{rnd.uniform(5, 900):.2f}', ts.strftime('%Y-%m-%d %H:%M:%S')])
print(f'{N} rows written to orders.csv')والملف compose.yaml يشغل السيرفرين على منفذين مختلفين مربوطين بالعنوان 127.0.0.1 فقط:
# The same schema and queries on MySQL and MariaDB, side by side (ArabRoot lab).
# docker compose -p lab-mysqlmaria up -d
name: lab-mysqlmaria
services:
mysql:
image: mysql:9.7.2
container_name: lab-mysqlmaria-mysql
environment:
MYSQL_ROOT_PASSWORD: lab-root-pass
MYSQL_DATABASE: shop
command: ["--local-infile=1"]
ports: ["127.0.0.1:33061:3306"]
volumes: ["./:/lab:ro", "mysql-data:/var/lib/mysql"]
mariadb:
image: mariadb:12.3.3
container_name: lab-mysqlmaria-mariadb
environment:
MARIADB_ROOT_PASSWORD: lab-root-pass
MARIADB_DATABASE: shop
ports: ["127.0.0.1:33062:3306"]
volumes: ["./:/lab:ro", "mariadb-data:/var/lib/mysql"]
volumes:
mysql-data:
mariadb-data:والجدول في الملف schema.sql، وهو نفسه للسيرفرين:
-- The same table on both servers
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
customer_id INT NOT NULL,
country CHAR(2) NOT NULL,
status VARCHAR(12) NOT NULL,
amount DECIMAL(10,2) NOT NULL CHECK (amount > 0),
created_at DATETIME NOT NULL,
extra JSON NULL,
KEY idx_customer (customer_id),
KEY idx_created (created_at)
);الآن نقوم بتوليد البيانات وتشغيل السيرفرين وإنشاء الجدول:
python gen_orders.py
docker compose -p lab-mysqlmaria up -d
docker exec -i lab-mysqlmaria-mysql mysql -uroot -plab-root-pass shop < schema.sql
docker exec -i lab-mysqlmaria-mariadb mariadb -uroot -plab-root-pass shop < schema.sqlنفس الجدول، وتعريفان مختلفان
الجدول أنشئ على الاثنين دون أي خطأ، ولكن لنطلب من كل سيرفر أن يرينا كيف فهمه بالأمر SHOW CREATE TABLE:
docker exec lab-mysqlmaria-mysql mysql -uroot -plab-root-pass shop -E -e "SHOW CREATE TABLE orders"
docker exec lab-mysqlmaria-mariadb mariadb -uroot -plab-root-pass shop -E -e "SHOW CREATE TABLE orders"والمخرج من MySQL سوف يكون كما يلي:
Create Table: CREATE TABLE `orders` (
`id` bigint NOT NULL,
`customer_id` int NOT NULL,
`country` char(2) NOT NULL,
`status` varchar(12) NOT NULL,
`amount` decimal(10,2) NOT NULL,
`created_at` datetime NOT NULL,
`extra` json DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_customer` (`customer_id`),
KEY `idx_created` (`created_at`),
CONSTRAINT `orders_chk_1` CHECK ((`amount` > 0))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ciوالمخرج من MariaDB:
Create Table: CREATE TABLE `orders` (
`id` bigint(20) NOT NULL,
`customer_id` int(11) NOT NULL,
`country` char(2) NOT NULL,
`status` varchar(12) NOT NULL,
`amount` decimal(10,2) NOT NULL CHECK (`amount` > 0),
`created_at` datetime NOT NULL,
`extra` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`extra`)),
PRIMARY KEY (`id`),
KEY `idx_customer` (`customer_id`),
KEY `idx_created` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_uca1400_ai_ciفي المخرج أعلاه لاحظ التالي:
- العمود
extraنوعهjsonفي MySQL، أما في MariaDB فهوlongtextمعCHECK (json_valid(...))، فالقيمة تحفظ نصاً ويتحقق السيرفر فقط أنها JSON صالح. - الترتيب الافتراضي للنصوص Collation مختلف:
utf8mb4_0900_ai_ciفي MySQL وutf8mb4_uca1400_ai_ciفي MariaDB، والاثنان لا يفرقان بين الحروف الكبيرة والصغيرة، ولكن المقارنة بين عمودين بترتيبين مختلفين في JOIN قد تمنع استخدام الفهرس. - MariaDB ما زال يكتب العرض
bigint(20)وint(11)، وMySQL توقف عن ذلك، وهذا شكلي فقط ولكنه يظهر في أي أداة تقارن تعريفات الجداول.
تحميل مليون طلب
نحمل الملف في الاثنين بالأمر LOAD DATA LOCAL INFILE، ونقيس الوقت من خارج ال Container:
L="LOAD DATA LOCAL INFILE '/lab/orders.csv' INTO TABLE orders FIELDS TERMINATED BY ',' (id,customer_id,country,status,amount,created_at)"
time docker exec lab-mysqlmaria-mysql mysql --local-infile=1 -uroot -plab-root-pass shop -e "$L"
time docker exec lab-mysqlmaria-mariadb mariadb --local-infile=1 -uroot -plab-root-pass shop -e "$L"والمخرج سوف يكون كما يلي (الأول MySQL والثاني MariaDB):
real 1m48.032s
real 0m58.479sثم ثلاثة استعلامات، كل واحد ثلاث مرات: مجموع المبيعات المدفوعة لكل دولة (يقرأ الجدول كله)، وطلبات عميل واحد (فهرس idx_customer)، وطلبات يوم واحد (فهرس idx_created). والوقت الذي يطبعه العميل بالخيار -vvv كان كما يلي:
| الاستعلام | MySQL 9.7.2 | MariaDB 12.3.3 |
|---|---|---|
| تحميل مليون صف | 1m48s | 58s |
| GROUP BY country على الجدول كله | 4.106 ثم 1.654 ثم 2.383 ثانية | 4.442 ثم 2.421 ثم 2.507 ثانية |
| طلبات عميل واحد بالفهرس | 0.001 ثانية | 0.001 إلى 0.002 ثانية |
| طلبات يوم واحد بالفهرس | 0.002 إلى 0.007 ثانية | 0.001 إلى 0.012 ثانية |
| حجم البيانات والفهارس بعد ANALYZE | 53 MB و67 MB | 53 MB و67 MB |
| الذاكرة بعد التحميل (docker stats) | 630.8 MiB | 234.1 MiB |
والنتيجة نفسها على السيرفرين، وهذا جزء منها:
country orders revenue
SA 87916 39867208.77
QA 87834 39609909.90
KW 87524 39566498.31وهل يعني هذا أن MariaDB أسرع في التحميل؟ لا يمكن أن نقول ذلك من تجربة واحدة بإعدادات افتراضية، والسبب أن الإعدادات الافتراضية نفسها مختلفة بين الاثنين، والاستعلامات بالفهرس متساوية تقريباً، واستعلام الجدول الكامل متقارب. والدرس الحقيقي من الجدول أن الأداء ليس هو ما يفرق بين الاثنين في تطبيق عادي، وإنما التوافق والميزات والرخص. أما الذاكرة فاستهلاك MySQL بعد التحميل كان قرابة ثلاثة أضعاف MariaDB بالإعدادات الافتراضية، وهذا يهم إذا كان السيرفر صغيراً، فراجع الإعدادات قبل أن تحكم.
الانكسار الأول: المعامل المختصر لل JSON
لنفرض أن التطبيق يحفظ كود الخصم في العمود extra ويقرؤه بالطريقة المختصرة التي يكتبها أغلب المطورين على MySQL:
UPDATE orders SET extra='{"coupon":"RAMADAN","items":3}' WHERE id=1;
SELECT id, extra->>'$.coupon' AS coupon FROM orders WHERE id=1;على MySQL يعطي RAMADAN، وعلى MariaDB يكون المخرج كما يلي:
ERROR 1064 (42000) at line 1: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '>>'$.coupon' AS coupon FROM orders WHERE id=1' at line 1والحل أن تكتب الدوال التي يفهمها الاثنان، فالأمران التاليان أعطيا RAMADAN على السيرفرين:
SELECT id, JSON_VALUE(extra,'$.coupon') AS coupon FROM orders WHERE id=1;
SELECT JSON_UNQUOTE(JSON_EXTRACT(extra,'$.coupon')) AS coupon FROM orders WHERE id=1;الانكسار الثاني: INSERT … RETURNING
والعكس أيضاً يحدث، فMariaDB يسمح لك بأن تحصل على الصف الذي أدخلته في نفس الأمر:
INSERT INTO orders (id,customer_id,country,status,amount,created_at)
VALUES (1000001,7,'SA','paid',50,NOW()) RETURNING id, created_at;والمخرج على MariaDB:
id created_at
1000001 2026-10-10 05:43:31أما على MySQL:
ERROR 1064 (42000) at line 1: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'RETURNING id, created_at' at line 1ولاحظ أن رقم الخطأ نفسه 1064 في الحالتين، وهو خطأ صياغة عام، فإذا رأيته بعد نقل تطبيق من سيرفر إلى آخر فابحث أولاً عن صياغة خاصة بالسيرفر القديم، وليس عن خطأ في الكود.
الانكسار الثالث: ملف النسخ من MySQL لا يدخل MariaDB
هذا أهم انكسار في التجربة، لأنه يحدث في يوم النقل نفسه. نصنع جدولاً صغيراً للعملاء في MySQL، ثم نأخذ منه نسخة بالأمر mysqldump كما هو، ونستوردها في MariaDB:
docker exec lab-mysqlmaria-mysql mysql -uroot -plab-root-pass shop -e "CREATE TABLE customers (id INT PRIMARY KEY, name VARCHAR(80)); INSERT INTO customers VALUES (1,'سارة'),(2,'Sara'),(3,'Ahmed');"
docker exec lab-mysqlmaria-mysql mysqldump -uroot -plab-root-pass shop customers > customers.sql
docker exec lab-mysqlmaria-mariadb mariadb -uroot -plab-root-pass -e "CREATE DATABASE fromdump"
docker exec -i lab-mysqlmaria-mariadb mariadb -uroot -plab-root-pass fromdump < customers.sqlوالمخرج سوف يكون كما يلي:
--------------
SET @@GLOBAL.GTID_PURGED=/*!80000 '+'*/ '77796506-c46c-11f1-8231-f68dd65c7de7:1-11'
--------------
ERROR 1193 (HY000) at line 24: Unknown system variable 'GTID_PURGED'والسبب أن ال GTID مفعل افتراضياً في MySQL 9.7، فالقيمة الافتراضية لـ gtid_mode هي ON بحسب توثيق متغيرات GTID، وتأكدنا من ذلك في السيرفر نفسه:
@@gtid_mode @@server_uuid
ON 77796506-c46c-11f1-8231-f68dd65c7de7ولذلك يضع mysqldump في أول الملف سطراً يخبر السيرفر الذي يستورد بأرقام المعاملات التي تمت، وهذا المتغير غير موجود في MariaDB لأن صيغة GTID عنده مختلفة. والأخطر هنا أن الاستيراد توقف عند السطر 24، أي بعد أن نفذ ما قبله، فإذا كان الملف فيه عدة قواعد قد تجد بعضها منقولاً وبعضها لا. والحل أن تطلب من mysqldump ألا يكتب هذا السطر:
docker exec lab-mysqlmaria-mysql mysqldump --set-gtid-purged=OFF -uroot -plab-root-pass shop customers > customers.sql
docker exec -i lab-mysqlmaria-mariadb mariadb -uroot -plab-root-pass fromdump < customers.sql
docker exec lab-mysqlmaria-mariadb mariadb -uroot -plab-root-pass fromdump -e "SHOW CREATE TABLE customers\G SELECT * FROM customers WHERE name='sara';"والمخرج:
Create Table: CREATE TABLE `customers` (
`id` int(11) NOT NULL,
`name` varchar(80) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
id name
2 Saraولاحظ هنا أمرين: الأول أن MariaDB 12.3 قبل الترتيب utf8mb4_0900_ai_ci القادم من MySQL، وهذا الترتيب كان سبب فشل كثير من عمليات النقل في الإصدارات الأقدم من MariaDB بالخطأ Unknown collation، فإذا كان السيرفر الذي تنقل إليه أقدم فتوقع هذا الخطأ واستبدل الترتيب في الملف. والثاني أن البحث عن 'sara' أعطى Sara على السيرفرين، فالترتيب لا يفرق بين الحروف الكبيرة والصغيرة على الاثنين.
الانكسار الرابع: عميل MariaDB يرفض الاتصال بـ MySQL
نستخدم الآن برنامج العميل mariadb من داخل ال Container الثاني للاتصال بسيرفر MySQL، وهذا ما يحدث عندما تستخدم أدوات MariaDB لإدارة سيرفر MySQL أثناء النقل:
docker exec lab-mysqlmaria-mariadb mariadb -h mysql -uroot -plab-root-pass -e "SELECT VERSION(), CURRENT_USER()"والمخرج:
ERROR 2026 (HY000): TLS/SSL error: self-signed certificate in certificate chainوالسبب أن العميل في MariaDB منذ 11.4 يتحقق من شهادة السيرفر افتراضياً، ومع سيرفر MariaDB ينجح هذا حتى مع شهادة موقعة ذاتياً Self-signed بطريقة تربط بصمة الشهادة بكلمة مرور المستخدم كما تشرح مدونة MariaDB، ولكن هذه الطريقة خاصة بسيرفر MariaDB، وMySQL ينشئ لنفسه شهادة موقعة ذاتياً عند أول تشغيل، فرفضها العميل. والحل الصحيح في الإنتاج أن تعطي MySQL شهادة من جهة تثق بها وتمرر ملفها للعميل، أما في المختبر فالخيار --skip-ssl-verify-server-cert يكفي:
VERSION() CURRENT_USER()
9.7.2 root@%ولاحظ أن المستخدم في MySQL يستخدم caching_sha2_password، وفي MariaDB mysql_native_password، كما يظهر من جدول المستخدمين في كل منهما:
MySQL: root caching_sha2_password
MariaDB: root mysql_native_passwordوهذا يعني أن المستخدمين لا ينتقلون مع كلمات مرورهم كما هي، فدليل النقل الرسمي يعيد تعيين كلمات المرور للمستخدمين الذين يستخدمون caching_sha2_password، فخطط لإنشاء المستخدمين من جديد في السيرفر الجديد.
الانكسار الخامس: مجلد البيانات
آخر شيء ننظر إليه هو الملفات نفسها على القرص:
docker exec lab-mysqlmaria-mysql ls -la /var/lib/mysql/shop/
docker exec lab-mysqlmaria-mariadb ls -la /var/lib/mysql/shop/والمخرج من MySQL ثم MariaDB:
-rw-r----- 1 mysql mysql 114688 Oct 10 05:43 customers.ibd
-rw-r----- 1 mysql mysql 134217728 Oct 10 05:43 orders.ibd
-rw-rw---- 1 mysql mysql 70 Oct 10 05:36 db.opt
-rw-rw---- 1 mysql mysql 2215 Oct 10 05:38 orders.frm
-rw-rw---- 1 mysql mysql 134217728 Oct 10 05:42 orders.ibdMariaDB يحتفظ بتعريف كل جدول في ملف .frm بجانب ملف البيانات، أما MySQL فلا يوجد عنده هذا الملف لأن التعريف في قاموس البيانات الداخلي، ولذلك لا تقم بالحل البسيط الذي كان يعمل قديماً، وهو أن توقف MySQL وتشغل MariaDB على نفس ال Volume، والسبب أن MariaDB لن يجد تعريف الجداول، وقد تخسر ال Volume إذا حاول إصلاحه. والطريقة الصحيحة دائماً نسخة منطقية ثم استعادة كما في قسم النقل.
حذف المختبر
docker compose -p lab-mysqlmaria down -v
rm orders.csv customers.sqlالنسخ المتماثل Replication والعناقيد
شرحنا أشكال العناقيد وفكرة الانتقال Failover بالتفصيل في مقال عناقيد قواعد البيانات، والمهم هنا هو الفرق بين الاثنين:
- النسخ الأساسي: الاثنان يرسلان سجل التغييرات Binary Log من السيرفر الرئيسي إلى النسخ، وكلاهما يدعم النسخ بالموقع File and Position والنسخ بال GTID، ولكن صيغة GTID مختلفة كما رأينا، ولذلك لا تستطيع أن تخلط بينهما بال GTID، وإذا احتجت أن يكون MariaDB نسخة من MySQL أثناء النقل فاستخدم النسخ بالموقع كما ينصح دليل النقل.
- العنقود بأكثر من سيرفر يكتب: في MariaDB هو Galera Cluster، وهو مدمج في الحزمة، وأصبح فريقه داخل MariaDB plc منذ 2025. وفي MySQL هو Group Replication ومعه InnoDB Cluster وMySQL Router.
- البروكسي: MySQL Router مفتوح تحت GPL، وMaxScale تحت BSL كما في جدول الرخص، فإذا كان لديك ثلاثة سيرفرات أو أكثر في الإنتاج وتريد MaxScale فاقرأ شروط الرخصة أولاً، أو استخدم بروكسي مفتوحاً مثل ProxySQL أو تجميع الاتصالات من جهة التطبيق.
متى تختار كل واحد منهما؟
MySQL
نقاط القوة: هو الأصل الذي تختبر عليه أغلب الأدوات والمكتبات والخدمات السحابية المدارة، ونوع JSON فيه حقيقي ويقارن كقيم، وإصدارات LTS تدعم ثماني سنوات، وGroup Replication وInnoDB Cluster وMySQL Router كلها مفتوحة.
نقاط الضعف: شركة واحدة تملك الكود وتقرر ما يدخل النسخة المجانية، والنسخ الاحتياطي الساخن وThread Pool والتدقيق في النسخة التجارية، واستهلاك الذاكرة الافتراضي أعلى، ونموذج Innovation يغير السلوك بين الإصدارات فلا تستخدمه في الإنتاج إلا إذا كنت تتابع كل إصدار.
MariaDB
نقاط القوة: النسخ الاحتياطي الساخن وThread Pool والتدقيق والتشفير كلها في النسخة المجانية، ومؤسسة مستقلة تحرس الكود، وGalera مدمج، وصياغة مفيدة مثل RETURNING وSequences، والحزمة default-mysql-server في Debian تثبت MariaDB.
نقاط الضعف: لم يعد بديلاً مباشراً لـ MySQL 8 وما بعده، وJSON نص وليس نوعاً ثنائياً، ودعم LTS المجاني ثلاث سنوات فقط، وبعض المنتجات التجارية والخدمات المدارة تدعم MySQL ولا تذكر MariaDB، وMaxScale ليس مفتوح المصدر.
دليل القرار
- تطبيق جاهز تستضيفه (WordPress وNextcloud وBookStack وغيرها): استخدم ما يذكره التطبيق في توثيقه أولاً، وإذا ذكر الاثنين فاختر الذي تعرف إدارته، فالفرق لن يظهر في هذه التطبيقات.
- تطبيق تكتبه أنت ويعتمد على JSON كثيراً: MySQL أنسب، أو فكر في PostgreSQL كما في مقال PostgreSQL لكل شيء.
- تريد كل الأدوات مجاناً دون نسخة تجارية: MariaDB أنسب.
- تريد عنقوداً يكتب على أكثر من سيرفر: Galera في MariaDB أبسط في البداية، ولكن اقرأ قسم Multi-Primary قبل أن تقرر أنك تحتاجه أصلاً.
- ستستخدم خدمة مدارة لاحقاً: تأكد أن الخدمة تقدم القاعدة التي تختارها اليوم بنفس الإصدار، حتى لا تحتاج نقلاً بين الاثنين في المستقبل.
- لديك تطبيق قائم يعمل: لا تنقله من واحدة إلى الأخرى لأن مقالاً قال إن الأخرى أفضل، فالنقل كما رأينا له ثمن، وانقل فقط عندما تحتاج ميزة أو رخصة لا توجد في الحالية.
وللمقارنة مع PostgreSQL وMongoDB على نفس البيانات راجع مقال PostgreSQL أم MySQL أم MongoDB؟.
النقل من MySQL إلى MariaDB دون مفاجآت
إذا قررت النقل، فهذه الخطوات بالترتيب، وكل خطوة جاءت من شيء انكسر في التجربة أو من دليل النقل الرسمي:
- ابحث في كود التطبيق عن المعامل
->و->>وعن أي دالة خاصة بـ MySQL، واستبدلها بـJSON_VALUEوJSON_EXTRACT. - شغل اختبارات التطبيق على MariaDB في بيئة منفصلة قبل أي شيء، فهي أسرع طريقة لتجد أخطاء 1064.
- خذ النسخة بالخيارات
--single-transaction --routines --events --triggers --hex-blob --set-gtid-purged=OFF، ولاحظ أن الخيار الأخير ليس في الدليل الرسمي ولكنه هو الذي حل خطأ 1193 في التجربة. - لا تنقل قاعدة
mysqlنفسها (المستخدمين والصلاحيات)، وأنشئ المستخدمين من جديد في MariaDB. - إذا كان السيرفر الجديد أقدم من 11.4 فابحث في الملف عن
utf8mb4_0900_ai_ciواستبدله بترتيب موجود عنده. - استورد، ثم قارن عدد الصفوف في كل جدول وبعض المجاميع بين السيرفرين قبل أن توجه التطبيق إلى الجديد.
- احتفظ بالنسخة القديمة واختبر الاستعادة منها كما في مقال النسخ الاحتياطي 3-2-1، فهي طريق الرجوع إذا ظهرت مشكلة بعد أيام.
والنقل في الاتجاه المعاكس من MariaDB إلى MySQL يمر بنفس الطريق مع مشاكل معكوسة: RETURNING وSequences وأي صياغة خاصة بـ MariaDB سوف تعطي 1064 على MySQL.
أخطاء شائعة
- تشغيل MariaDB على مجلد بيانات MySQL 8: لا يعمل، والنقل يكون بنسخة منطقية فقط.
- استيراد نسخة
mysqldumpدون--set-gtid-purged=OFF: يتوقف الاستيراد في المنتصف بالخطأ 1193 بعد أن نفذ جزءاً منه. - الاعتماد على
->>في تطبيق يقول إنه يدعم الاثنين: يعمل في بيئتك على MySQL ويفشل عند أول عميل يستخدم MariaDB. - اختيار إصدار Innovation من MySQL للإنتاج: يدعم حتى الإصدار التالي فقط وقد يغير السلوك، فاستخدم LTS.
- نسيان أن دعم MariaDB LTS المجاني ثلاث سنوات: خطط للترقية مبكراً، أو قم بشراء الدعم الممتد.
- تعطيل التحقق من الشهادة في الإنتاج لأن العميل رفض الاتصال: أصلح الشهادة بدل أن تطفئ التحقق.
الخلاصة
- MariaDB خرج من MySQL في 2009 بعد أن اشترت Oracle شركة Sun، واليوم MySQL تملكها Oracle، وMariaDB تملكها K1 مع مؤسسة مستقلة تحرس الكود.
- السيرفر في الاثنين تحت GPLv2، والفرق في الأدوات حوله: النسخ الساخن وThread Pool والتدقيق مجانية في MariaDB وتجارية في MySQL، وMaxScale تحت BSL.
- نفس البروتوكول ونفس InnoDB وأغلب SQL، ولكن في التجربة انكسر
->>على MariaDB، وRETURNING على MySQL، وملف النسخ من MySQL توقف في MariaDB بسبب GTID_PURGED، والعميل رفض الشهادة الموقعة ذاتياً، ومجلد البيانات لا يشارك. - الأداء على مليون طلب متقارب في الاستعلامات، والفرق ليس سبباً كافياً للاختيار.
- اختر ما يذكره تطبيقك، ولا تنقل تطبيقاً قائماً إلا لميزة أو رخصة تحتاجها، وإذا نقلت فبنسخة منطقية واختبار كامل.
دليلأنواع قواعد البيانات ومتى تختار كل نوعالفرق بين قواعد البيانات العلائقية والمستندات والمفتاح والقيمة وغيرها، وأي الأسئلة تجيب عنها كل عائلة بسهولة.
دليلعناقيد قواعد البيانات Database Clustering ومتى تحتاجهاماذا تعني كلمة Cluster في كل قاعدة بيانات، ومتى تحتاج نسخاً أو انتقالاً تلقائياً، ومتى يكفيك سيرفر واحد مع نسخ احتياطية جيدة.
دليلPostgreSQL أم MySQL أم MongoDB؟مقارنة عادلة بين أشهر ثلاث قواعد بيانات على نفس البيانات، وأسئلة بسيطة تساعدك على اختيار ما يناسب مشروعك.
دليلواجهات إدارة قواعد البيانات: pgAdmin وAdminer وCloudBeaverكيف تختار واجهة ويب لإدارة قاعدة بياناتك وتشغلها، ولماذا يجب ألا تصل إليها من الإنترنت إلا عبر باب واحد محمي.
دليلالنسخ الاحتياطي بقاعدة 3-2-1 واختبار الاستعادةكيف تحدد كم بيانات تقبل أن تخسر، وتبني نسخاً لا يستطيع المخترق حذفها، وتتأكد أنك تستطيع استعادتها فعلاً.سجل التحديثات
- أكتوبر 2026: كتابة المقال، والتجربة على MySQL 9.7.2 وMariaDB 12.3.3 في Docker Engine 29.8.0، والمعلومات من توثيق MySQL وMariaDB وبيانات MariaDB plc ومؤسسة MariaDB كما قرأناها في 10 و11 أكتوبر 2026.