0%

MySQL CLion Debug

简介

之前Debug MySQL都是用的gdb,还是有诸多不便,主要体现在不能快速查看当前堆栈时的一些变量值,以及不能配合代码上下文查看,尝试用CLion做Debug。

安装步骤

前提是需要安装cmake,由于我的电脑安装cmake一直有问题,暂时用CLion自带的cmake

1
/Applications/CLion.app/Contents/bin/cmake/mac/bin/cmake

并执行下面的命令进行编译

1
2
3
4
5
6
wget https://github.com/mysql/mysql-server/archive/5.6.zip
unzip 5.6.zip
cd mysql-server-5.6/
cmake -DCMAKE_INSTALL_PREFIX=/your_path/mysql -DMYSQL_DATADIR=/your_path/mysql/data -DSYSCONFDIR=/your_path/mysql -DMYSQL_UNIX_ADDR=/your_path/mysql/mysql.sock -DWITH_DEBUG=1 -DDOWNLOAD_BOOST=1 -DWITH_BOOST=/your_path/mysql-server/ -DDOWNLOAD_BOOST_TIMEOUT=60000
make -j 4
make install -j 4

上面编译流程可能遇见问题,VERSION文件会被boost库误读,需要将VERSION加上MYSQL前缀,并修改cmake/mysql_version.cmake里的文件名。
之后使用编译完的程序初始化库(在basedir里新建一个data目录用于存放数据)

1
scripts/mysql_install_db –user=mysql –basedir=/your_path/mysql

初始化的脚本跑完后去目录下check,确保表都有了,不然启动后会报,启动的时候启动mysqld,这个是可以debug的可执行文件,启动参数为

1
--user=mysql --basedir=/Users/wudan03/software/mysql-server

这个地方我遇到了问题,但是现在已经忘了
建一个my.cnf文件,这个忘记是在哪用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# For advice on how to change settings please see
# http://dev.mysql.com/doc/refman/5.6/en/server-configuration-defaults.html

[mysqld]

# Remove leading # and set to the amount of RAM for the most important data
# cache in MySQL. Start at 70% of total RAM for dedicated server, else 10%.
# innodb_buffer_pool_size = 128M

# Remove leading # to turn on a very important data integrity option: logging
# changes to the binary log between backups.
# log-bin = /Users/danwu/software/mysql-server-5.6/log/mysql-bin.log
log-bin = mysql-bin
general-log = /Users/danwu/software/mysql-server-5.6/log/mysql-log.log
log-error = /Users/danwu/software/mysql-server-5.6/log/mysql_error.log

# These are commonly set, remove the # and set as required.
basedir = /Users/danwu/software/mysql-server-5.6
datadir = /Users/danwu/software/mysql-server-5.6/data
port = 3306
server_id = 1
socket = /Users/danwu/software/mysql-server-5.6/mysql.sock

# Remove leading # to set options mainly useful for reporting servers.
# The server defaults are faster for transactions and fast SELECTs.
# Adjust sizes as needed, experiment to find the optimal values.
# join_buffer_size = 128M
# sort_buffer_size = 2M
# read_rnd_buffer_size = 2M

sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES

然后使用CLion打来工程,选中CMakeLists.txt即可,找到sql/main.cc,在run下面Edit Configuration,增加参数 –user=mysql,右键debug即可
遇到的一个耽误时间的问题是,我之前安装过mysql不过版本不一致,使用的配置文件是 /etc/my.cnf,导致一直报错,后来在当前文件夹建了上面的my.cnf就好了,具体有点忘了

小试牛刀

拿 Reapeatable Read 隔离级别的重要代码实现看看
首先是在开始查询之前会调用 trx_assign_read_view 创建 read_view,最终会调用到 read_view_open_now_low

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// read0read.c
static
read_view_t*
read_view_open_now_low(
/*===================*/
trx_id_t cr_trx_id, /*!< in: trx_id of creating
transaction, or 0 used in purge */
mem_heap_t* heap) /*!< in: memory heap from which
allocated */
{
read_view_t* view;
ulint n_trx = UT_LIST_GET_LEN(trx_sys->rw_trx_list);

ut_ad(mutex_own(&trx_sys->mutex));

view = read_view_create_low(n_trx, heap);

view->undo_no = 0;
view->type = VIEW_NORMAL;
view->creator_trx_id = cr_trx_id;

/* No future transactions should be visible in the view */

view->low_limit_no = trx_sys->max_trx_id;
view->low_limit_id = view->low_limit_no;

/* No active transaction should be visible, except cr_trx */

ut_list_map(trx_sys->rw_trx_list, &trx_t::trx_list, CreateView(view));

if (view->n_trx_ids > 0) {
/* The last active transaction has the smallest id: */
view->up_limit_id = view->trx_ids[view->n_trx_ids - 1];
} else {
view->up_limit_id = view->low_limit_id;
}

/* Purge views are not added to the view list. */
if (cr_trx_id > 0) {
read_view_add(view);
}

return(view);
}

上述代码中 CreateView 负责创建read_view。
在之后遍历B-tree的过程中,会通过下面的函数判断数据可见性,大致是如果当前事物id小于read_view的低水位时,表示当前记录是可见的,如果大于高水位了,则当前记录是不可见了,这里low和high其实颠倒了。另外注意到 trx_ids 其实是当前不可见的事务id数组,代码通过二分查找从中查找当前事务id,如果存在则不可见

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// read0read.ic
UNIV_INLINE
bool
read_view_sees_trx_id(
/*==================*/
const read_view_t* view, /*!< in: read view */
trx_id_t trx_id) /*!< in: trx id */
{
if (trx_id < view->up_limit_id) {

return(true);
} else if (trx_id >= view->low_limit_id) {

return(false);
} else {
ulint lower = 0;
ulint upper = view->n_trx_ids - 1;

ut_a(view->n_trx_ids > 0);

do {
ulint mid = (lower + upper) >> 1;
trx_id_t mid_id = view->trx_ids[mid];

if (mid_id == trx_id) {
return(FALSE);
} else if (mid_id < trx_id) {
if (mid > 0) {
upper = mid - 1;
} else {
break;
}
} else {
lower = mid + 1;
}
} while (lower <= upper);
}

return(true);
}

上面可见性的判断可以用下面的图来解释,其中trx_id小于upper_limit_id的都是可见的,表示在read_view创建之前就已经commit的记录,而所有大于low_limit_id的记录都是不可见的,因为创建read_view的时候这些记录还没写入,low_limit_id是创建read_view时的最大trx_id。而在两者中间的,存在一批正在运行中,还没commit的事务,可以认为是兄弟事务,这些也是不可见的,这些保存在read_view->trx_ids中,除了这些正在进行中的事务,其他的都是可见的。
mysql_read_view
为了更加明确表达意思,图画的有点夸张,实际上up_limit_id和low_limit_id中间的read_view->trx_ids只会有当前还没提交的事务id,当事务结束之后老的版本会被清理掉,一个记录只会有一个trx_id的版本的数据。
对于之前讨论过的RR隔离级别情况下的幻读问题,由于执行update的时候,会将记录的trx_id更新未当前trx_id,而当前trx_id是小于up_limit_id的,因此变成了可见,导致产生幻读。

总结

CLion可以支持跳进inline代码,GDB应该也可以,可能需要配置。

Reference

  1. 在Mac上使用CLion编译MySQL
  2. InnoDB Repeatable Read隔离级别之大不同
  3. Innodb MVCC实现原理
  4. MySQL简介
如果您觉得这些内容对您有帮助,你可以赞助我以提高站点的文章质量