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

от автора

Начиная с версии 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 как следует из названия организует данные в кучу и реализует поддержку хэш-индексов. В обоих случаях приходится решать вспомогательную проблему высвобождения свободных участков памяти после удаления строк, которую нам удалось избежать за счёт использования односвязного списка.
P.S. И напоследок хочу порекомендовать утилиту ctags для тех, кто с ней не знаком. С её помощью довольно удобно изучать код, разбросанный по разным файлам, например она позволяет прыгнуть к определению функции или переменной по CTRL+]. Есть поддержка vim, emacs и других популярных редакторов. Здесь можно найти краткое руководство.

ссылка на оригинал статьи http://habrahabr.ru/post/210068/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *