Как стать автором
Обновить

Движок на MySQL за 5 минут

Время на прочтение4 мин
Количество просмотров17K
Начиная с версии 5.1 в MySQL реализована поддержка динамически подключаемых плагинов. А дистрибутив содержит примерный скелет кода под названием – example. Он описывает интерфейс и структуру базового обработчика – handler, копия которого создается отдельно для каждого соединения с БД. Также ему передаётся указатель на дескриптор таблицы TABLE *table и вспомогательный вектор TABLE_SHARE *share, используемый для синхронизации с другими обработчиками. Разработку плагина можно осуществлять по модульному принципу, реализуя только необходимые функции в первую очередь и закрывая заглушками более сложные операции.
Поскольку шаблон example описывает только интерфейс и не выполняет никаких операций, то в этом примере мы добавим в него реализацию CRUD-операций на основе одно-связного списка. Для этого необходимо написать всего 4 функции:

  • int rnd_init(bool scan);
  • int rnd_next(uchar *buf);
  • int write_row(uchar *buf);
  • int delete_row(const uchar *buf);

Подготовка окружения
Шаблон example располагается в двух файлах ha_example.h и ha_example.cc в директории storage/example из исходного кода MySQL. В этом примере будет использован MySQL Community 5.5.35. Сначала скопируем example и переименуем в smalldb.

wget http://downloads.mysql.com/archives/get/file/mysql-5.5.35.tar.gz
tar -zxvf mysql-5.5.35.tar.gz
cd mysql-5.5.35
cp -rf storage/example storage/smalldb
cd storage/smalldb
sed -e 's/EXAMPLE/SMALLDB/g' -e 's/example/smalldb/g' ha_example.h > ha_smalldb.h
sed -e 's/EXAMPLE/SMALLDB/g' -e 's/example/smalldb/g' ha_example.cc > ha_smalldb.cc
sed -i 's/EXAMPLE/SMALLDB/g;s/example/smalldb/g' CMakeLists.txt


Начнём с того, что добавим заготовку для односвязного списка и несколько указателей — на голову, текущий, предыдущий и следующий элемент списка (в ha_smalldb.h):
class node{ 
  public:  
    uchar* data;  // Содержимое строки
    node* next;  // Указатель на следующую строку
  node(){
    data=NULL;
    next=NULL;
  }
  ~node(){
    free(data);
  }
};

node *first, *cur, *prev, *next;
int row_count, cur_pos;

void append_node(node* n){
  if (first==NULL){
    first=n;
  }else{
    node* iter=first;
    while (iter->next!=NULL){iter=iter->next;}
    iter->next=n;
  }
}

Теперь перейдём к операции записи – write_row(uchar *buf). В качестве аргумента ей передаётся buf, который содержит в себе содержимое добавляемой строки во внутреннем представлении:


Поскольку мы будем сохранять данные в память без предварительной обработки, то можно просто скопировать содержимое строки целиком. Длину строки с метаданными можно получить из дескриптора таблицы table->s->reclength.
int ha_smalldb::write_row(uchar *buf)
{
  DBUG_ENTER("ha_smalldb::write_row");

  node* n=new node();
  n->data = (uchar*) malloc(sizeof(uchar)*table->s->reclength);
  memcpy(n->data,buf,table->s->reclength);

  append_node(n);
  row_count++;

  DBUG_RETURN(0);
}

При чтении данных из таблицы используется последовательное сканирование с помощью связки — rnd_init и rnd_next. Оно используется если движок не поддерживает индексов или первичных ключей. Для начала в rnd_init() необходимо подготовить указатели.
int ha_smalldb::rnd_init(bool scan)
{
  DBUG_ENTER("ha_smalldb::rnd_init");

  cur=NULL;
  prev=NULL;
  next=first;
  cur_pos=0;

  DBUG_RETURN(0);
}

Булевый аргумент scan сообщает о намерении БД произвольного (непоследовательного) чтения таблицы. Мы можем его спокойно проигнорировать, т.к. при попытке вызвать нереализованную команду rnd_pos(), БД получит в ответ HA_ERR_WRONG_COMMAND и догадается, что придётся читать попорядку.
После одиночного rnd_init() БД будет последовательно вызывать rnd_next(), пока не дойдет до конца таблицы — HA_ERR_END_OF_FILE. После каждого вызова память по адресу buf должна быть заполнена содержимым строки во внутреннем представлении.
int ha_smalldb::rnd_next(uchar *buf)
{
  int rc;
  DBUG_ENTER("ha_smalldb::rnd_next");
  MYSQL_READ_ROW_START(table_share->db.str, table_share->table_name.str, TRUE);

  if (next!=NULL){
    prev=cur;
    cur=next;
    next=cur->next;
    memcpy(buf,cur->data,table->s->reclength);
    cur_pos++;
    rc=0;
  }else{
    rc= HA_ERR_END_OF_FILE;
  }

  MYSQL_READ_ROW_DONE(rc);
  DBUG_RETURN(rc);
}

И наконец завершим написанием delete_row() путём удаления текущего элемента из списка:
int ha_smalldb::delete_row(const uchar *buf)
{
  DBUG_ENTER("ha_smalldb::delete_row");

  if (cur!=first){
    free(cur);
    prev->next=next;
  }else{
    free(cur);
    cur=NULL;
    first=next;
  }
  row_count--;

  DBUG_RETURN(0);
}

Вот и всё, теперь можно испытать мини-движок в деле.
Установка
В MySQL 5.5 для сборки кода используется cmake. Для установки достаточно скомпилировать .so файл и скопировать его в директорию с плагинами установленной версии MySQL. Узнать её можно с помощью команды — SHOW VARIABLES LIKE "%plugin_dir%";
Если сама MySQL не установлена или установлена другая версия, то необходимо дополнительно установить БД с помощью make install.

#Из корневой директории mysql-5.5.35:
cmake .
cd storage/smalldb
make
cp ha_smalldb.so /opt/mysql/server-5.5.35/lib/plugin/
#Из консоли MySQL: 
INSTALL PLUGIN smalldb SONAME "ha_smalldb.so";
CREATE DATABASE small;
USE small;
CREATE TABLE tbl (a VARCHAR(255), b VARCHAR(255)) ENGINE=smalldb;
INSERT  INTO tbl values ("Hello","Habr");
SELECT * FROM tbl;




Помимо INSERT и SELECT можно выполнять даже более сложные SQL-запросы, использующие операторы LIKE, INNER JOIN и пр., ведь оптимизация SQL – запросов реализована на уровне ядра БД до передачи управления движку, но её эффективность конечно же будет зависеть от поддерживаемого движком функционала.
Надеюсь этот материал позволит немного познакомиться с интерфейсом подключаемых модулей MySQL. Полностью код проекта можно скачать на github.
Для тех, кто хочет более серъезно изучить внутренности движков MySQL, но не рискует пока подступиться к InnoDB, советую обратить внимание на CSV или HEAP (Новое название — MEMORY). CSV добавляет возможность сохранения данных на диск в одноименном формате. А HEAP как следует из названия организует данные в кучу и реализует поддержку хэш-индексов. В обоих случаях приходится решать вспомогательную проблему высвобождения свободных участков памяти после удаления строк, которую нам удалось избежать за счёт использования односвязного списка.
Более подробное руководство о написании собственных движков можно найти в главе 22 официальной документации MySQL Internals Manual. Chapter 22. Writing a Custom Storage Engine.
P.S. И напоследок хочу порекомендовать утилиту ctags для тех, кто с ней не знаком. С её помощью довольно удобно изучать код, разбросанный по разным файлам, например она позволяет прыгнуть к определению функции или переменной по CTRL+]. Есть поддержка vim, emacs и других популярных редакторов. Здесь можно найти краткое руководство.
Теги:
Хабы:
Всего голосов 50: ↑45 и ↓5+40
Комментарии10

Публикации

Истории

Ближайшие события

7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань