Quá trình tạo một storage engine mới

Một phần của tài liệu Nghiên cứu phương pháp xây dựng Storage Engine cho hệ quản trị cơ sở dữ liệu MySQL (Trang 62)

L ỜI CAM ĐOAN

3.5.Quá trình tạo một storage engine mới

Một storage engine có thể chứa nhiều tính năng khác nhau và chắc chắn nó không

một cách hợp lí để có thể đạt được thành công trong một khoảng thời gian chấp nhận được.

Theo Charles A. Bell trong [4], tiến trình tạo một storage engine nên tuân theo một

dãy các giai đoạn. Tùy thuộc vào những tính năng cần xây dựng cho storage engine,

tiến trình có thể không cần hoàn thiện tất cả các giai đoạn. Về mặt chức năng, một

storage engine nên hỗ trợ tối thiểu các chức năng được xác định trong 3 hoặc 4 giai

đoạn đầu. Các giai đoạn gồm:

- Giai đoạn 1: Tạo engine sơ khai– tạo ra một storage engine cơ sở có thể tra lắp

dễ dàng với máy chủ. Tại bước này, các tệp mã nguồn cơ bản được tạo ra, storage engine được thiết lập như là một dẫn xuất của lớp handler cơ sở, và storage engine bản thân nó được tra lắp vào trong mã nguồn của máy chủ.

- Giai đoạn 2: Làm việc với các bảng – đảm bảo storage engine có thể tạo, mở,

đóng và xóa các tệp. Đây là bước thiết lập các thường trình quản lí các tệp cơ sở và

đảm bảo rằng engine làm việc với tệp một cách đúng đắn.

- Giai đoạn 3: Đọc và ghi dữ liệu – Là tính năng cơ bản nhất của một storage engine. Đây là bước tạo ra các phương thức để đọc dữ liệu được lưu trữ theo định dạng

của phương tiện lưu trữ và dịch chúng sang định dạng dữ liệu bên trong MySQL, và

ngược lại, đọc dữ liệu từ định dạng bên trong MySQL sang định dạng khác trên các phương tiện lưu trữ.

- Giai đoạn 4: Chỉnh sửa và xóa dữ liệu – Để storage engine sử dụng được trong các ứng dụng, cần triển khai các phương thức cho phép thay đổi dữ liệu trong storage

engine như là chỉnh sửa và xóa dữ liệu.

- Giai đoạn 5: Lập chỉ mục dữ liệu – một storage engine đầy đủ chức năng nên bao gồm khả năng cho phép đọc nhanh ngẫu nhiên và hạn chế phạm vi truy vấn. Bước

này triển khai quá trình lập chỉ mục dữ liệu.

- Giai đoạn 6: Bổ sung các hỗ trợ giao tác – là bước cuối cùng và phức tạp nhất. Đây là bước làm cho storage engine trở thành một cơ chế lưu trữ CSDL quan hệ phù hợp để sử dụng trong môi trường giao tác.

Quá trình xây dựng storge engine nên được thực hiện tuần tự từ giai đoạn đầu cho đến giai đoạn cuối cùng. Tại mỗi giai đoạn, nên kiểm thử và gỡ lỗi theo bất kì cơ chế

nào.

Nhằm hỗ trợ người phát triển xây dựng storage engine mới, MySQL cung cấp một

storage engine có tên là Example chứa một số phương thức cần thiết cho một storage

engine trong giai đoạn thứ nhất. Quá trình xây dựng storage engine được triển khai cụ

Giai đoạn 1: Tạo một engine sơ khai. Một engine sơ khai sẽ chứa các phép toán cơ bản nhất cho phép lựa chọn engine trong câu lệnh CREATE và tạo ra tệp cấu trúc

của bảng (tệp .frm). Tại bước này, phải đảm bảo có những thay đổi mã khởi tạo cần

thiết để đăng kí storage engine với máy chủ. Ở đây, sử dụng Example để tạo một

storage engine mới. Các bước thực hiện như sau:

Bước 1: Tạo tệp mã nguồn của engine cần tạo

Đầu tiên, tạo một thư mục có tên là tên của storage engine muốn tạo (giả sử đặt tên là myabc) trong thư mục /storage. (adsbygoogle = window.adsbygoogle || []).push({});

Các tệp mã nguồn thường có gồm: một tệp mã nguồn (hay một class) được đặt tên lần lượt là: ha_<tên engine>.c (hoặc .cpp) và một tệp header có tên là ha_<tên engine>.h (trong đó, ha_ thể hiện các tệp này được dẫn xuất từ lớp handler). Các tệp

mã nguồn phải nằm trong thư mục /storage/<tên storage engine>.

Như vậy các tệp mã nguồn của myabc nằm trong thư mục /storage/myabc nên có gồm: ha_myabc.hha_myabc.cc

Các thao tác thực hiện cụ thể như sau:

- Sao chép tệp *.cc và *.h từ thư mục /storage/example sang thư mục chứa storage

engine muốn tạo (/storage/myabc) và đổi tất cả các từ example hoặc EXAMPLE về tên của storage engine muốn tạo tương ứng (đổi từ example sang myabc).

- Trong tệp ha_myabc.cc, bổ sung khai báo kiểu dữ liệu cho cấu trúc handlerton

của myabc như sau:

handlerton *myabc_hton;

static int myabc_init_func(void *p) { DBUG_ENTER("myabc_init_func"); myabc_hton= (handlerton *)p; (void) my_hash_init(&myabc_open_tables,system_charset_info,32,0,0, (my_hash_get_key) myabc_get_key,0,0); myabc_hton->state= SHOW_OPTION_YES; myabc_hton->db_type= DB_TYPE_MYABC_DB; myabc_hton->create= myabc_create_handler; ………. }

Khai báo lớp ha_myabc dẫn xuất từ lớp handler như sau:

class ha_myabc: public handler {

THR_LOCK_DATA lock; ///< MySQL lock MYABC_SHARE *share; ///< Shared lock info public:

ha_myabc(handlerton *hton, TABLE_SHARE *table_arg); ~ha_myabc()

……… }

Bước 2: Bổ sung các tệp nguồn vào các tệp dự án MySQL

- Mục đích của bước này là báo cho trình biên dịch cần biên dịch storage engine như một phần của máy chủ. Do đó, cần sửa đổi các tệp cấu hình.

Để thực hiện được cần: trong tệp cấu hình của storage engine, phải khai báo được

các thông tin về mã nguồn, các headers của storage engines đồng thời trong kịch bản

cấu hình của máy chủ, cần đảm bảo cơ chế kiểm tra được storage engine mới được bổ

sung (sử dụng trong lựa chọn –DWITH_<TÊN ENGINE>_STORAGE_ ENGINE=1 của CMake)

Việc sửa đổi các tệp cấu hình phụ thuộc vào công cụ được sử dụng, MySQL sử

dụng công cụ autotools để cấu hình, xây dựng và cài đặt MySQL 5.1 nhưng từ phiên bản 5.5 lại sử dụng CMake thay thế cho autotools. Mỗi công cụ có kịch bản cấu hình

riêng. Các thao tác thay đổi kịch bản cấu hình khi sử dụng autotools có thể tìm hiểu

chi tiết trong [4, pp. 297 – 300]. Trong phần này, việc xây dựng storage engine được

xây dựng trên MySQL 5.5.17 trên hệ điều hành Linux sử dụng CMake để cấu hình.

Khi đó cần sao chép các tệp Cmakelist.txt trong /example sang thư mục /myabc và khai báo các tham số cho MYSQL_ADD_PLUGIN theo cấu trúc như sau:

MYSQL_ADD_PLUGIN (plugin_name source1… source N [STORAGE_ENGINE] //định nghĩa cho storage engine

[MANDATORY|DEFAULT] //định nghĩa cho các plugin bắt buộc. nếu lựa chọn //default thì luôn được biên dịch trừ khi sử dụng lựa chọn

//_WITHOUT_<plugin_name>. [STATIC_ONLY|MODULE_ONLY] (adsbygoogle = window.adsbygoogle || []).push({});

// chỉ được xây dựng như một thư viện tĩnh thư viện động [MODULE_OUTPUT_NAME module_name] // xác định tên thư viện plugin khi nó //được xây dựng như một mô-đun chia sẻ

[STATIC_OUTPUT_NAME static_name]

[RECOMPILE_FOR_EMBEDDED] //cần được xây dựng với cờ

// -DEMBEDDED_SERVER để sử dụng với máy chủ nhúng. [LINK_LIBRARIES lib1 … libN] // các thư viện được liên kết với plugin

[DEPENDENCIES target1 … targetN] // các phụ thuộc của plugin

Ví dụ Cmakelist.txt của CSV như sau:

SET(CSV_PLUGIN_STATIC "csv") SET(CSV_PLUGIN_MANDATORY TRUE)

SET(CSV_SOURCES ha_tina.cc ha_tina.h transparent_file.cc transparent_file.h) MYSQL_ADD_PLUGIN(csv ${CSV_SOURCES} STORAGE_ENGINE MANDATORY)

Hay tệp Cmakelist.txt của Example như sau:

SET(EXAMPLE_PLUGIN_DYNAMIC "ha_example") SET(EXAMPLE_SOURCES ha_example.cc)

MYSQL_ADD_PLUGIN(example ${EXAMPLE_SOURCES} STORAGE_ENGINE MODULE_ONLY)

Thay đổi kịch bản cấu hình bằng cách thêm vào tệp config.h.cmake trong thư mục

gốc của MySQL dòng lệnh:

#cmakedefine WITH_CSV_STORAGE_ENGINE 1

#cmakedefine WITH_MYABC_STORAGE_ENGINE 1

………

Bước 3: Bổ sung engine vào máy chủ

Mục đích của bước này là thêm vào các khai báo cần thiết để máy chủ MySQL

nhận biết và làm việc được với storage engine.

Các tệp cần chỉnh sửa gồm:

/sql/handler.h: bổ sung một entry khác cho legacy_db_type cho phép handler xác nhận kiểu bảng cho engine, như sau:

enum legacy_db_type {

DB_TYPE_UNKNOWN=0,DB_TYPE_DIAB_ISAM=1, DB_TYPE_HASH,DB_TYPE_MISAM,DB_TYPE_PISAM, DB_TYPE_RMS_ISAM, DB_TYPE_HEAP, DB_TYPE_ISAM,

DB_TYPE_MRG_ISAM, DB_TYPE_MYISAM, DB_TYPE_MRG_MYISAM, DB_TYPE_BERKELEY_DB, DB_TYPE_INNODB,

DB_TYPE_GEMINI, DB_TYPE_NDBCLUSTER,

DB_TYPE_EXAMPLE_DB, DB_TYPE_ARCHIVE_DB, DB_TYPE_CSV_DB, DB_TYPE_FEDERATED_DB,

DB_TYPE_BLACKHOLE_DB, DB_TYPE_PARTITION_DB, DB_TYPE_BINLOG,

DB_TYPE_DEFAULT=127 // Must be last };

/sql/set_var.h: bổ sung thiết lập cho biến have_myabc như sau:

extern SHOW_COMP_OPTION have_csv, have_innodb, have_myabc; (adsbygoogle = window.adsbygoogle || []).push({});

/sql/sys_var.cc: bổ sung khai báo cho biến have_myabc như sau:

export SHOW_COMP_OPTION have_csv, have_myabc, have_innodb= SHOW_OPTION_DISABLED;

static Sys_var_have Sys_have_myabc( "have_myabc", "have_myabc",

READ_ONLY GLOBAL_VAR(have_myabc), NO_CMD_LINE);

/sql/mysqld.cc: thêm khai báo cho biến have_myabc, cách đơn giản là tìm từ khóa csv, sao chép các đoạn lệnh và chỉnh sửa thành myabc, đoạn lệnh được chỉnh sửa như

sau: have_myabc= plugin_status(STRING_WITH_LEN("myabc"), MYSQL_STORAGE_ENGINE_PLUGIN); ……… #ifdef WITH_MYABC_STORAGE_ENGINE if (opt_bootstrap) log_output_options= LOG_FILE; else logger.init_log_tables();

if (log_output_options & LOG_NONE) {

/*

Issue a warining if there were specified additional options to the log-output along with NONE. Probably this wasn't what user wanted. */

if ((log_output_options & LOG_NONE) && (log_output_options & ~LOG_NONE)) sql_print_warning("There were other values specified to "

"log-output besides NONE. Disabling slow " "and general logs anyway.");

logger.set_handlers(LOG_FILE, LOG_NONE, LOG_NONE); }

else {

/* fall back to the log files if tables are not present */

LEX_STRING myabc_name={C_STRING_WITH_LEN("myabc")};

if (!plugin_is_ready(&myabc_name, MYSQL_STORAGE_ENGINE_PLUGIN)) {

sql_print_error("MYABC engine is not present, falling back to the " "log files");

log_output_options= (log_output_options & ~LOG_TABLE) | LOG_FILE; /* purecov: end */

}

logger.set_handlers(LOG_FILE, opt_slow_log ? log_output_options:LOG_NONE, opt_log ? log_output_options:LOG_NONE);

} #else

logger.set_handlers(LOG_FILE, opt_slow_log ? LOG_FILE:LOG_NONE, opt_log ? LOG_FILE:LOG_NONE);

#endif

Sau khi thực hiện xong tất cả các bước, thao tác cuối cùng là biên dịch mã nguồn

và kiểm thử cho giai đoạn này. Các lệnh biên dịch như sau:

Cmake –DWITH_MYABC_STORAGE_ENGINE=1 Make

Make install

Sau đó khởi động máy chủ MySQL và kiểm thử bằng các lệnh sau:

Mysql> show storage engines; Mysql> use test;

Mysql>create table <tên bảng>(<cột1> <kiểu dữ liệu>,…) ENGINE= myabc; Mysql> show create table <tên bảng>; (adsbygoogle = window.adsbygoogle || []).push({});

Giai đoạn 2: Làm việc với các bảng. Mục đích của giai đoạn này là khiến cho storage engine đã được tạo trong giai đoạn 1 có thể tạo, mở, đóng và xóa các tệp dữ

liệu. Trong giai đoạn này, cần thiết lập các chương trình con quản lí tệp cơ sở và thiết

lập rằng engine sẽ làm việc với các tệp một cách đúng đắn. MySQL cung cấp một số chương trình vào/ra tệp đóng gói trong các hàm mức thấp. Chi tiết về các hàm chức năng này có thể tìm thấy trong thư mục /mysys. Một số hàm thường dùng: my_create (): tạo tệp, my_open (): mở tệp, my_read(): đọc dữ liệu từ tệp, my_write (): ghi dữ liệu

lên tệp, my_delete (): xóa tệp.

Trong giai đoạn này cần chỉnh sửa tệp mã nguồn (tệp ha_myabc.cc) với các phương thức tương ứng:

1/ static MYABC_SHARE *get_share(const char *table_name, TABLE *table) {….}

3/ int ha_myabc::create(const char *name, TABLE *table_arg, HA_CREATE_INFO *create_info) {….}

4/ int ha_myabc::open(const char *name, int mode, uint test_if_locked) {…} 5/ int ha_myabc::delete_table(const char *name) {…}

6/ int ha_myabc::rename_table(const char * from, const char * to) {…}

Khai báo phần mở rộng của tệp được tạo bởi storage engine bằng cách khai báo

phần mở rộng và lưu chúng trong mảng ha_myabc_exts[] như sau:

#define ABC_EXT ".abc"

static const char *ha_myabc_exts[] = { ABC_EXT,

NullS };

Cuối cùng, biên dịch lại mã nguồn và kiểm thử với lệnh rename table <tên cũ> to <tên mới>; drop table <tên bảng>;

Giai đoạn 3: Đọc và ghi dữ liệu. Mục đích của giai đoạn này làm cho storage

engine đã được tạo trong giai đoạn 2 có thể đọc và ghi dữ liệu.

Để storage engine có thể thực hiện phép toán đọc cần thay đổi tiến trình đọc dữ

liệu cơ bản bằng cách tạo các thay đổi thích hợp trong các phương thức rnd_init() - thiết lập các điều kiện ban đầu để duyệt bảng, rnd_next() – trả về bản ghi tiếp theo

trong tệp hoặc phát hiện kết thúc tệp, position() – trả về con trỏ trỏ tới vị trí của bản

ghi hiện tại, rnd_pos() – tìm kiếm vị trí hiện tại và đọc bản ghi tại vị trí đó. Các phương thức này nằm trong tệp ha_myabc.cc

Để thực hiện các phép ghi – chỉ cần thay đổi phương thức write_row() ghi dữ liệu

theo từng dòng.

Chỉnh sửa tệp ha_myabc.h: do phương thức position() yêu cầu lưu trữ một con trỏ

cũng như một vị trí bản ghi offset hoặc một giá trị khóa được sử dụng để sắp xếp, cho

nên cần bổ sung biến current_position cho lớp ha_myabc như sau:

class ha_myabc: public handler {

THR_LOCK_DATA lock; ///< MySQL lock MYABC_SHARE *share; ///< Shared lock info

my_off_t current_position; /// vi tri hien thoi trong tep khi duyet tep ……

Chỉnh sửa tệp ha_myabc.cc: để engine có thể đọc và ghi dữ liệu, cần chỉnh sửa các phương thức sau: (adsbygoogle = window.adsbygoogle || []).push({});

1/ int ha_myabc::rnd_init(bool scan) {…} 2/ int ha_myabc::rnd_next(uchar *buf) {…}

3/ void ha_myabc::position(const uchar *record) {…} 4/ int ha_myabc::rnd_pos(uchar *buf, uchar *pos) {…} 5/ int ha_myabc::info(uint flag) {…}

6/ int ha_myabc::write_row(uchar *buf) {…}

Cuối cùng, biên dịch lại và kiểm thử với các lệnh:

INSERT INTO <tên bảng> VALUES (…); SELECT * FROM <tên bảng>;

Giai đoạn 4: Chỉnh sửa và xóa dữ liệu. Mục đích của giai đoạn này là khiến cho

storage engine đã được tạo trong giai đoạn 3 có khả năng chỉnh sửa và xóa dữ liệu trên các bảng.

Khi chỉnh sửa dữ liệu, dữ liệu cũ sẽ bị ghi đè bởi dữ liệu mới. Quá trình xóa phức

tạp hơn, có thể chỉ là đánh dấu dữ liệu đã bị xóa và bỏ qua các bản ghi đã bị đánh dấu khi đọc. Điều này sẽ gây lãng phí nhiều bộ nhớ trong trường hợp có nhiều dòng bị xóa và chèn vào. Để tránh tình huống đó, có thể thường xuyên lưu dữ liệu và bỏ bảng, sau

đó tải lại dữ liệu từ bản lưu đó. Điều đó giúp loại bỏ các dòng trống nên không lãng phí không gian nhớ nhưng triển khai phức tạp hơn. Phụ thuộc vào ứng dụng cần xây

dựng, lựa chọn cách thức phù hợp nhất.

Chỉnh sửa tệp ha_myabc.cc: để storage engine có thể chỉnh sửa và xóa các dòng dữ liệu, cần có những thay đổi cần thiết trên các phương thức update_row(), delete_row(),delete_all_row() mà không cần sửa đổi tệp header.

Các phương thức cần thay đổi là:

1/ int ha_myabc::update_row(const uchar *old_data, uchar *new_data){…} 2/ int ha_myabc::delete_row(const uchar *buf){…}

3/ int ha_myabc::delete_all_rows(){…}

Cuối cùng là dịch lại mã nguồn và kiểm thử với lệnh:

UPDATE <tên bảng> SET <tên cột>=<giá trị> WHERE <điều kiện>; DELETE FROM <tên bảng> WHERE <điều kiện>;

Giai đoạn 5: Lập chỉ mục cho dữ liệu. Mục đích của giai đoạn này là khiến cho storage engine đã tạo được trong bước 4 có khả năng hỗ trợ chỉ mục. Việc lập chỉ mục

kĩ thuật chỉ mục rất phong phú và phức tạp cho nên tùy thuộc vào từng ứng dụng cụ

thể mà lựa chọn kĩ thuật phù hợp và hiệu quả nhất.

Có 6 phương thức phải được triển khai để hoàn thiện cơ chế lập chỉ mục tại giai đoạn này, gồm:

Phương thức index_read(): Thiết lập một bộ đệm dòng (row buffer) cho các dòng trong tệp tương thích với khóa đưa ra. Nếu kết quả tìm ra là null, thì phương thức này nên trả ra giá trị khóa đầu tiên trong tệp đó.

Phương thức index_read_idx(): tương tự như phương thức index_read() nhưng được gọi từ các phần khác của trình tối ưu. Phương thức này thiết lập một bộ đệm

dòng cho dòng trong tệp phù hợp với khóa tìm kiếm. Nếu quá trình tìm kiếm trả ra giá

trị null thì phương thức nên trả ra giá trị khóa đầu tiên và dòng đầu tiên trong tệp đó. Phương thức index_next(): lấy khóa kế tiếp của chỉ mục và trả ra các dòng thỏa

mãn từ tệp dữ liệu. Nó được gọi tới trong quá trình duyệt vùng chỉ mục. Dùng phương

thức index_next() và thêm các mã phù hợp để lấy khóa kế tiếp từ chỉ mục đó và đọc

một dòng từ tệp dữ liệu.

Phương thức index_prev(): lấy khóa phía trước chỉ mục và trả ra các dòng phù hợp từ tệp dữ liệu. Nó được gọi tới trong quá trình duyệt vùng chỉ mục.

Phương thức index_first(): trả ra khóa đầu tiên trong tệp chỉ mục. Phương thức index_last(): trả ra khóa cuối cùng trong tệp chỉ mục. (adsbygoogle = window.adsbygoogle || []).push({});

Chỉnh sửa tệp ha_<tên engine>.cc: vì chỉ mục sẽ ảnh hưởng tới mọi hành vi đã có của storage engine đã tạo sau giai đoạn 4. Do đó, cần bổ sung các đoạn lệnh thích

hợp trong các cấu trúc get_share(), free_share(), các phương thức create(), open(),

close(), write_row(), update_row(),… đã được xây dựng trong các giai đoạn trước để đảm bảo chúng hỗ trợ chỉ mục.

Sau đó, chỉnh sửa các phương thức:

1/ virtual int index_read(uchar * buf, const uchar * key, uint key_len, enum ha_rkey_function find_flag) { ……}

2/ virtual int index_read_idx_map(uchar * buf, uint index, const uchar * key, key_part_map keypart_map,

enum ha_rkey_function find_flag) {………} 3/ virtual int index_next(uchar * buf) {….. }

4/ virtual int index_prev(uchar * buf) { …..} 5/ virtual int index_first(uchar * buf){ …..} 6/ virtual int index_last(uchar * buf) { …..}

Giai đoạn 6: Bổ sung hỗ trợ giao tác. Đảm bảo storage engine có khả năng hỗ

trợ giao tác. Cơ sở cho triển khai giao tác trong storage engine được hỗ trợ bởi phương

thức start_stmt()external_lock().

Bắt đầu giao tác: sử dụng các phương thức:

Phương thức virtual int start_stmt(THD *thd, thr_lock_type lock_type) {return 0;}: được gọi tới khi bắt đầu một giao tác, tạo một savepoint và đăng kí giao tác với

máy chủ sử dụng phương thức trans_register_ha().

Phương thức int handler::ha_external_lock(THD *thd, int lock_type): chỉ định

một khóa cho một bảng. MySQL gọi tới phương thức này cho mọi bảng đang sử dụng

Một phần của tài liệu Nghiên cứu phương pháp xây dựng Storage Engine cho hệ quản trị cơ sở dữ liệu MySQL (Trang 62)