摘要:
stonedb-在派生表的场景查询为空无法传递默认值-问题分析.
本文对该问题的成因, 相关功能的代码设计, 在下一步设计时如何应对这种问题, 做相关的分析。
https://stoneatom.yuque.com/staff-ft8n1u/lsztbl/rxlhws22n0f1otxn/edit#AqyB
相关ISSUE: https://github.com/stoneatom/stonedb/issues/1784
查询SQL:
SELECT 'aaaaa' inner_code,
a.exchange_rate,
sum(a.balance) AS balance,
'b' std_balance
FROM (SELECT b.inner_code,
CASE
WHEN r.row_id IS NOT NULL THEN
r.exchange_rate
ELSE
1.0000
END AS exchange_rate,
c.fiscal_date,
c.balance
FROM c1md_bank_acct a
JOIN c1md_company b
ON a.company_id = b.row_id
AND b.deleted_flag = '0'
LEFT JOIN c1cd_exchange_rate r
ON a.currency_id = r.currency_id
AND r.deleted_flag = '0'
AND r.using_flag = '1'
AND r.inure_date <= now()
AND (r.abate_date >= now() OR r.abate_date IS NULL)
LEFT JOIN (SELECT b.account_id, b.fiscal_date, b.balance
FROM (SELECT account_id, max(fiscal_date) fiscal_date
FROM c1am_acct_day
WHERE deleted_flag = '0'
GROUP BY account_id) a
JOIN c1am_acct_day b
ON a.account_id = b.account_id
AND a.fiscal_date = b.fiscal_date
WHERE b.deleted_flag = '0') c
ON a.row_id = c.account_id
WHERE a.deleted_flag = '0'
AND a.acct_flag IN ('2', '4')
AND a.company_id IN (3000000000027265)) a;
处理该问题的思路分析:
- 对于复杂的查询SQL, 涉及的操作符过多, 首先要进行问题定位
- 缩减到具体哪些操作符的执行出现了问题
- 这个过程涉及条件削减, 排查, 甚至摸索试探
- 找到出问题的操作符的对应的代码实现, 分析该处代码的设计意图,以及为什么会导致当前查询出错
- 该过程涉及架构能力,代码能力,以及理解他人代码设计意图的能力
- 对于复杂的代码, 牵扯到的因素过多,存在试探来猜测设计意图的情况, 但是一定需要事实的验证来辅助对设计意图的定位
- 重新对该操作符做设计
- 需要注意要兼容此前的设计意图
- 为了避免出现类似的问题, 需要评估代码修改对其他模块的影响
削减查询SQL的操作符
tianmu引擎的查询SQL
create table t1 (age int) engine=tianmu;
select case when age IS NOT NULL THEN age else 33 end, sum(age) from ( select * from t1) ta;
innodb引擎的查询SQL
create table t1_innodb (age int) engine=innodb;
select case when age IS NOT NULL THEN age else 33 end, sum(age) from ( select * from t1_innodb) ta;
mysql/sql和innodb执行分析:
执行过程的trace日志:
2022-10-18 mysql-5.7-开启debug日志_mysql 5.7 debug_财阀悟世的博客-CSDN博客
https://download.csdn.net/download/adofsauron/87864107
思路分析:
- 该debug的trace日志包含这个查询SQL的所有关键函数的执行过程
- 从这个日志中可以分析出mysql/sql层所有的查询优化和查询执行的细节
- 但是将问题聚焦下, 当前的目标是解决tianmu查询出错, 那么对应的就需要关注mysql/sql在处理case属性时是如何处理的
- 也就是说,要将重点,放在mysql/sql层是如何处理case的item的
- 这里需要注意, 分析case的item并不是为了要记住这块代码的处理逻辑, 而是作为一个正确处理case的解决方案的对标, 分析tianmu引擎在处理case的属性时的逻辑,从而有个逻辑处理的对比
- tianmu引擎对于case的属性有自己的一套处理逻辑, 在此处分析的一个歧途就是容易将mysql/sql当作唯一正确的接, 从而照抄mysql/sql的方案, 而忽略了tianmu引擎对于列属性处理的特殊性, 此特殊的地方在以下分析tianmu引擎对查询执行时具体分析
mysql/sql层的核心处理:
Item_func_case::fix_fields
调用堆栈:
#0 Item_func_case::fix_fields (this=0x7fd254005850, thd=0x7fd254000e10, ref=0x7fd254005ac0) at /root/work/stonedb-dev-20230605/sql/item_cmpfunc.cc:4001
#1 0x00000000023a8a7e in setup_fields (thd=0x7fd254000e10, ref_pointer_array=..., fields=..., want_privilege=1, sum_func_list=0x7fd254004d30, allow_sum_func=true, column_update=false)
at /root/work/stonedb-dev-20230605/sql/sql_base.cc:9138
#2 0x00000000024650da in st_select_lex::prepare (this=0x7fd254004bd0, thd=0x7fd254000e10) at /root/work/stonedb-dev-20230605/sql/sql_resolver.cc:200
#3 0x000000000247165f in handle_query (thd=0x7fd254000e10, lex=0x7fd254003138, result=0x7fd254006a88, added_options=0, removed_options=0, is_optimize_after_tianmu=0, free_join_from_tianmu=0)
at /root/work/stonedb-dev-20230605/sql/sql_select.cc:139
#4 0x0000000002427ae2 in execute_sqlcom_select (thd=0x7fd254000e10, all_tables=0x7fd254014a88) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:5205
#5 0x0000000002420e1e in mysql_execute_command (thd=0x7fd254000e10, first_level=true) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:2847
#6 0x0000000002428b0d in mysql_parse (thd=0x7fd254000e10, parser_state=0x7fd519f6ff90) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:5642
#7 0x000000000241db04 in dispatch_command (thd=0x7fd254000e10, com_data=0x7fd519f70730, command=COM_QUERY) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:1495
#8 0x000000000241c945 in do_command (thd=0x7fd254000e10) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:1034
#9 0x000000000254eeb5 in handle_connection (arg=0x965db20) at /root/work/stonedb-dev-20230605/sql/conn_handler/connection_handler_per_thread.cc:313
#10 0x0000000002c1e6f4 in pfs_spawn_thread (arg=0x9490860) at /root/work/stonedb-dev-20230605/storage/perfschema/pfs.cc:2197
#11 0x00007fd569c2b1ca in start_thread () from /lib64/libpthread.so.0
#12 0x00007fd566aa1e73 in clone () from /lib64/libc.so.6
函数实现:
bool Item_func_case::fix_fields(THD *thd, Item **ref)
{
/*
buff should match stack usage from
Item_func_case::val_int() -> Item_func_case::find_item()
*/
uchar buff[MAX_FIELD_WIDTH*2+sizeof(String)*2+sizeof(String*)*2+sizeof(double)*2+sizeof(longlong)*2];
bool res= Item_func::fix_fields(thd, ref);
/*
Call check_stack_overrun after fix_fields to be sure that stack variable
is not optimized away
*/
if (check_stack_overrun(thd, STACK_MIN_SIZE, buff))
return TRUE; // Fatal error flag is set!
return res;
}
参数ref的数据:
(gdb) p ref[0][0]
$4 = (Item_func_case) {
<Item_func> = {
<Item_result_field> = {
<Item> = {
<Parse_tree_node> = {
_vptr.Parse_tree_node = 0x4355418 <vtable for Item_func_case+16>,
contextualized = true,
transitional = false
},
members of Item:
is_expensive_cache = -1 '\377',
rsize = 0,
str_value = {
m_ptr = 0x0,
m_length = 0,
m_charset = 0x44664e0 <my_charset_bin>,
m_alloced_length = 0,
m_is_alloced = false
},
item_name = {
<Name_string> = {
<Simple_cstring> = {
m_str = 0x7fd2540065e0 "case when age IS NOT NULL THEN age else 33 end",
m_length = 47
}, <No data fields>},
members of Item_name_string:
m_is_autogenerated = true
},
orig_name = {
<Name_string> = {
<Simple_cstring> = {
m_str = 0x0,
m_length = 0
}, <No data fields>},
members of Item_name_string:
m_is_autogenerated = true
},
next = 0x7fd2540059d0,
max_length = 0,
marker = 0,
decimals = 0 '\000',
maybe_null = 0 '\000',
null_value = 0 '\000',
unsigned_flag = 0 '\000',
with_sum_func = 0 '\000',
fixed = 0 '\000',
collation = {
collation = 0x44664e0 <my_charset_bin>,
--Type <RET> for more, q to quit, c to continue without paging--
derivation = DERIVATION_COERCIBLE,
repertoire = 3
},
cmp_context = 4294967295,
runtime_item = false,
derived_used = false,
with_subselect = 0 '\000',
with_stored_program = 0 '\000',
tables_locked_cache = false,
is_parser_item = true
},
members of Item_result_field:
result_field = 0x0
},
members of Item_func:
args = 0x7fd2540059b8,
tmp_arg = {0x8f8f8f8f8f8f8f8f, 0x8f8f8f8f8f8f8f8f},
const_item_cache = 143,
allowed_arg_cols = 1,
used_tables_cache = 10344644715844964239,
not_null_tables_cache = 10344644715844964239,
arg_count = 3
},
members of Item_func_case:
first_expr_num = -1,
else_expr_num = 2,
cached_result_type = INT_RESULT,
left_result_type = INT_RESULT,
tmp_value = {
m_ptr = 0x0,
m_length = 0,
m_charset = 0x44664e0 <my_charset_bin>,
m_alloced_length = 0,
m_is_alloced = false
},
ncases = 2,
cmp_type = 2408550287,
cmp_collation = {
collation = 0x44664e0 <my_charset_bin>,
derivation = DERIVATION_NONE,
repertoire = 3
},
cached_field_type = 2408550287,
cmp_items = {0x0, 0x0, 0x0, 0x0, 0x0},
case_item = 0x0
}
(gdb) p ( (Item_func*) ref[0]).args[0][0]
$8 = (Item_func_isnotnull) {
<Item_bool_func> = {
<Item_int_func> = {
<Item_func> = {
<Item_result_field> = {
<Item> = {
<Parse_tree_node> = {
_vptr.Parse_tree_node = 0x4351ba0 <vtable for Item_func_isnotnull+16>,
contextualized = true,
transitional = false
},
members of Item:
is_expensive_cache = -1 '\377',
rsize = 0,
str_value = {
m_ptr = 0x0,
m_length = 0,
m_charset = 0x44664e0 <my_charset_bin>,
m_alloced_length = 0,
m_is_alloced = false
},
item_name = {
<Name_string> = {
<Simple_cstring> = {
m_str = 0x0,
m_length = 0
}, <No data fields>},
members of Item_name_string:
m_is_autogenerated = true
},
orig_name = {
<Name_string> = {
<Simple_cstring> = {
m_str = 0x0,
m_length = 0
}, <No data fields>},
members of Item_name_string:
m_is_autogenerated = true
},
next = 0x7fd254005850,
max_length = 21,
marker = 0,
decimals = 0 '\000',
maybe_null = 0 '\000',
null_value = 0 '\000',
unsigned_flag = 0 '\000',
with_sum_func = 0 '\000',
fixed = 0 '\000',
--Type <RET> for more, q to quit, c to continue without paging--
collation = {
collation = 0x446eae0 <my_charset_latin1>,
derivation = DERIVATION_NUMERIC,
repertoire = 1
},
cmp_context = 4294967295,
runtime_item = false,
derived_used = false,
with_subselect = 0 '\000',
with_stored_program = 0 '\000',
tables_locked_cache = false,
is_parser_item = true
},
members of Item_result_field:
result_field = 0x0
},
members of Item_func:
args = 0x7fd254005678,
tmp_arg = {0x7fd2540063a0, 0x8f8f8f8f8f8f8f8f},
const_item_cache = 143,
allowed_arg_cols = 1,
used_tables_cache = 10344644715844964239,
not_null_tables_cache = 10344644715844964239,
arg_count = 1
}, <No data fields>},
members of Item_bool_func:
m_created_by_in2exists = false
},
members of Item_func_isnotnull:
abort_on_null = false
}
(gdb) p ( (Item_func*) ref[0]).args[1][0]
$9 = (Item_field) {
<Item_ident> = {
<Item> = {
<Parse_tree_node> = {
_vptr.Parse_tree_node = 0x43485f8 <vtable for Item_field+16>,
contextualized = true,
transitional = false
},
members of Item:
is_expensive_cache = -1 '\377',
rsize = 0,
str_value = {
m_ptr = 0x0,
m_length = 0,
m_charset = 0x44664e0 <my_charset_bin>,
m_alloced_length = 0,
m_is_alloced = false
},
item_name = {
<Name_string> = {
<Simple_cstring> = {
m_str = 0x7fd2540056a8 "age",
m_length = 3
}, <No data fields>},
members of Item_name_string:
m_is_autogenerated = true
},
orig_name = {
<Name_string> = {
<Simple_cstring> = {
m_str = 0x0,
m_length = 0
}, <No data fields>},
members of Item_name_string:
m_is_autogenerated = true
},
next = 0x7fd2540056b0,
max_length = 0,
marker = 0,
decimals = 0 '\000',
maybe_null = 0 '\000',
null_value = 0 '\000',
unsigned_flag = 0 '\000',
with_sum_func = 0 '\000',
fixed = 0 '\000',
collation = {
collation = 0x44664e0 <my_charset_bin>,
derivation = DERIVATION_IMPLICIT,
--Type <RET> for more, q to quit, c to continue without paging--
repertoire = 3
},
cmp_context = 4294967295,
runtime_item = false,
derived_used = false,
with_subselect = 0 '\000',
with_stored_program = 0 '\000',
tables_locked_cache = false,
is_parser_item = true
},
members of Item_ident:
orig_db_name = 0x0,
orig_table_name = 0x0,
orig_field_name = 0x7fd2540056a8 "age",
m_alias_of_expr = false,
context = 0x7fd254004c30,
db_name = 0x0,
table_name = 0x0,
field_name = 0x7fd2540056a8 "age",
cached_field_index = 4294967295,
cached_table = 0x0,
depended_from = 0x0
},
members of Item_field:
table_ref = 0x0,
field = 0x0,
result_field = 0x0,
item_equal = 0x0,
no_const_subst = false,
have_privileges = 0,
any_privileges = false
}
函数目的分析:
- 此处是构建case属性的相关列属性的过程, ref的参数包含两种列属性
- 参与case运算的 Item_func_isnotnull
- 表的列属性Item_field
- 此时尚未读取列属性Item_field中的值进行计算取值, 目的在于构建case列属性相关信息的完整结构
Item_func_case::val_int
调用堆栈:
#0 Item_func_case::val_int (this=0x7fd254005850) at /root/work/stonedb-dev-20230605/sql/item_cmpfunc.cc:3897
#1 0x0000000001dde09f in Item_copy_int::copy (this=0x7fd2540217b0, thd=0x7fd254000e10) at /root/work/stonedb-dev-20230605/sql/item.cc:4868
#2 0x00000000023e3292 in copy_fields (param=0x7fd254020ff8, thd=0x7fd254000e10) at /root/work/stonedb-dev-20230605/sql/sql_executor.cc:4381
#3 0x000000000247902f in JOIN::clear (this=0x7fd254020ec0) at /root/work/stonedb-dev-20230605/sql/sql_select.cc:3365
#4 0x00000000023dfb7b in end_send_group (join=0x7fd254020ec0, qep_tab=0x7fd254021730, end_of_records=true) at /root/work/stonedb-dev-20230605/sql/sql_executor.cc:3060
#5 0x00000000023db32a in sub_select (join=0x7fd254020ec0, qep_tab=0x7fd2540215b8, end_of_records=true) at /root/work/stonedb-dev-20230605/sql/sql_executor.cc:1233
#6 0x00000000023dae9e in do_select (join=0x7fd254020ec0) at /root/work/stonedb-dev-20230605/sql/sql_executor.cc:959
#7 0x00000000023d8ded in JOIN::exec (this=0x7fd254020ec0) at /root/work/stonedb-dev-20230605/sql/sql_executor.cc:206
#8 0x00000000024717e3 in handle_query (thd=0x7fd254000e10, lex=0x7fd254003138, result=0x7fd254006a88, added_options=0, removed_options=0, is_optimize_after_tianmu=0, free_join_from_tianmu=0)
at /root/work/stonedb-dev-20230605/sql/sql_select.cc:195
#9 0x0000000002427ae2 in execute_sqlcom_select (thd=0x7fd254000e10, all_tables=0x7fd254014a88) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:5205
#10 0x0000000002420e1e in mysql_execute_command (thd=0x7fd254000e10, first_level=true) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:2847
#11 0x0000000002428b0d in mysql_parse (thd=0x7fd254000e10, parser_state=0x7fd519f6ff90) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:5642
#12 0x000000000241db04 in dispatch_command (thd=0x7fd254000e10, com_data=0x7fd519f70730, command=COM_QUERY) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:1495
#13 0x000000000241c945 in do_command (thd=0x7fd254000e10) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:1034
#14 0x000000000254eeb5 in handle_connection (arg=0x965db20) at /root/work/stonedb-dev-20230605/sql/conn_handler/connection_handler_per_thread.cc:313
#15 0x0000000002c1e6f4 in pfs_spawn_thread (arg=0x9490860) at /root/work/stonedb-dev-20230605/storage/perfschema/pfs.cc:2197
#16 0x00007fd569c2b1ca in start_thread () from /lib64/libpthread.so.0
#17 0x00007fd566aa1e73 in clone () from /lib64/libc.so.6
函数实现:
longlong Item_func_case::val_int()
{
assert(fixed == 1);
char buff[MAX_FIELD_WIDTH];
String dummy_str(buff,sizeof(buff),default_charset());
Item *item=find_item(&dummy_str);
longlong res;
if (!item)
{
null_value=1;
return 0;
}
res=item->val_int();
null_value=item->null_value;
return res;
}
函数实现的运算逻辑的数据:
函数目的分析:
- 该函数实现了case属性的取值和计算, 代码逻辑非常简单直接,这里不做过多分析
- 值得注意的是该函数被调用的位置, 是在end_send_group中, 由copy_fields完成
- 需要注意myql/sql中的item的概念, 是作为一个基本元素来理解, 即使是一个包含case运算的列, 也是一个item. 而这个包含case运算的item又包含了其他属性的item。在当前场景下具体为 Item_func_case
- item对上层提供的统一抽象接口是val_int和val_str, 即使是逻辑分支运算例如case, 也是被包括在了val_int中进行
tianmu执行分析:
构建查询序列:
T:-1 = TABLE_ALIAS(T:0,"t1")
T:-2 = TMP_TABLE(T:4294967295)
VC:-2.0 = CREATE_VC(T:-2,EXPR("case"))
A:-1 = T:-2.ADD_COLUMN(VC:-2.0,LIST,"case when age IS NOT NULL THEN age else 33 end","ALL"
VC:-2.1 = CREATE_VC(T:-2,PHYS_COL(T:-1,A:0))
A:-2 = T:-2.ADD_COLUMN(VC:-2.1,SUM,"sum(age)","ALL")
T:-2.APPLY_CONDS()
RESULT(T:-2)
tianmu的构建查询序列的分析思路:
- 需要注意这个日志仅仅是将mysql/sql的查询树, 转换成tianmu的查询序列的过程, 是一个构建的过程,而非执行查询序列的过程
- 查询序列的执行的入口是APPLY_CONDS()
- 查询序列的每一个操作符的具体的执行过程, 需要从APPLY_CONDS()入口开始逐步定位
- 具体到本问题, 就是要分析EXPR的列属性,是被如何具体处理
tianmu执行的核心处理:
MysqlExpression::EvalType
调用堆栈:
#0 Tianmu::core::MysqlExpression::EvalType (this=0x7fd254024e70, tv=0x7fd254eb5bd8) at /root/work/stonedb-dev-20230605/storage/tianmu/core/mysql_expression.cpp:373
#1 0x0000000002e406d2 in Tianmu::vcolumn::ExpressionColumn::ExpressionColumn (this=0x7fd254eb5a30, expr=0x7fd254024e70, temp_table=0x7fd254eb56f0, temp_table_alias=-2,
multi_index=0x7fd254026e40) at /root/work/stonedb-dev-20230605/storage/tianmu/vc/expr_column.cpp:72
#2 0x0000000002cfd078 in Tianmu::core::Query::CreateColumnFromExpression (this=0x7fd519f6e750, exprs=std::vector of length 1, capacity 1 = {...}, temp_table=0x7fd254eb56f0,
temp_table_alias=-2, mind=0x7fd254026e40) at /root/work/stonedb-dev-20230605/storage/tianmu/core/query.cpp:498
#3 0x0000000002cff9d5 in Tianmu::core::Query::Preexecute (this=0x7fd519f6e750, qu=..., sender=0x71c5c40, display_now=true) at /root/work/stonedb-dev-20230605/storage/tianmu/core/query.cpp:820
#4 0x0000000002cebb38 in Tianmu::core::Engine::Execute (this=0x7218d50, thd=0x7fd254000e10, lex=0x7fd254003138, result_output=0x7fd254006a68, unit_for_union=0x0)
at /root/work/stonedb-dev-20230605/storage/tianmu/core/engine_execute.cpp:513
#5 0x0000000002cea825 in Tianmu::core::Engine::HandleSelect (this=0x7218d50, thd=0x7fd254000e10, lex=0x7fd254003138, result=@0x7fd519f6edc8: 0x7fd254006a68, setup_tables_done_option=0,
res=@0x7fd519f6edc4: 0, is_optimize_after_tianmu=@0x7fd519f6edbc: 1, tianmu_free_join=@0x7fd519f6edc0: 1, with_insert=0)
at /root/work/stonedb-dev-20230605/storage/tianmu/core/engine_execute.cpp:243
#6 0x0000000003084b13 in Tianmu::DBHandler::ha_my_tianmu_query (thd=0x7fd254000e10, lex=0x7fd254003138, result_output=@0x7fd519f6edc8: 0x7fd254006a68, setup_tables_done_option=0,
res=@0x7fd519f6edc4: 0, is_optimize_after_tianmu=@0x7fd519f6edbc: 1, tianmu_free_join=@0x7fd519f6edc0: 1, with_insert=0)
at /root/work/stonedb-dev-20230605/storage/tianmu/sql/ha_my_tianmu.cpp:95
#7 0x0000000002427aa8 in execute_sqlcom_select (thd=0x7fd254000e10, all_tables=0x7fd254014a88) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:5204
#8 0x0000000002420e1e in mysql_execute_command (thd=0x7fd254000e10, first_level=true) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:2847
#9 0x0000000002428b0d in mysql_parse (thd=0x7fd254000e10, parser_state=0x7fd519f6ff90) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:5642
#10 0x000000000241db04 in dispatch_command (thd=0x7fd254000e10, com_data=0x7fd519f70730, command=COM_QUERY) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:1495
#11 0x000000000241c945 in do_command (thd=0x7fd254000e10) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:1034
#12 0x000000000254eeb5 in handle_connection (arg=0x965db20) at /root/work/stonedb-dev-20230605/sql/conn_handler/connection_handler_per_thread.cc:313
#13 0x0000000002c1e6f4 in pfs_spawn_thread (arg=0x9490860) at /root/work/stonedb-dev-20230605/storage/perfschema/pfs.cc:2197
#14 0x00007fd569c2b1ca in start_thread () from /lib64/libpthread.so.0
#15 0x00007fd566aa1e73 in clone () from /lib64/libc.so.6
函数实现:
DataType MysqlExpression::EvalType(TypOfVars *tv) {
// set types of variables (_tianmufieldsCache)
if (tv) {
DataType fieldtype;
auto tianmufield_set = tianmu_fields_cache.begin();
while (tianmufield_set != tianmu_fields_cache.end()) {
auto it = tv->find(tianmufield_set->first);
if (it != tv->end()) {
for (auto &tianmufield : tianmufield_set->second) {
fieldtype = it->second;
tianmufield->SetType(fieldtype);
}
}
tianmufield_set++;
}
}
函数目的分析:
- 最大的一个问题便是为什么要在这个函数打断点做追踪?
- 对expr的处理的代码并不熟悉, 不知道tianmu具体是怎么处理case的, 无法直接在逻辑处理的地方打断点, 或者说对代码的熟悉程度不足以直接定位问题。这也是现在大部分的解决问题的时候面临的难题, 对这块代码的熟悉程度不足, 是在解决这块代码的问题时才第一次理解该块逻辑的代码
- 但是EvalType是必定要走到的, 必须要拿到expr的类型
- 从这个函数的调用堆栈出发, 理解expr的属性的处理流程
MysqlExpression::GetTianmufieldItem
调用堆栈:
#0 Tianmu::core::MysqlExpression::GetTianmufieldItem (this=0x7fd254024e70, ifield=0x7fd254eb2a08) at /root/work/stonedb-dev-20230605/storage/tianmu/core/mysql_expression.cpp:165
#1 0x00000000030c5a05 in Tianmu::core::MysqlExpression::TransformTree (this=0x7fd254024e70, root=0x7fd254eb2a08, dir=Tianmu::core::MysqlExpression::TransformDirection::FORWARD)
at /root/work/stonedb-dev-20230605/storage/tianmu/core/mysql_expression.cpp:240
#2 0x00000000030c5f09 in Tianmu::core::MysqlExpression::TransformTree (this=0x7fd254024e70, root=0x7fd254eb2f50, dir=Tianmu::core::MysqlExpression::TransformDirection::FORWARD)
at /root/work/stonedb-dev-20230605/storage/tianmu/core/mysql_expression.cpp:333
#3 0x00000000030c5e05 in Tianmu::core::MysqlExpression::TransformTree (this=0x7fd254024e70, root=0x7fd2540055c0, dir=Tianmu::core::MysqlExpression::TransformDirection::FORWARD)
at /root/work/stonedb-dev-20230605/storage/tianmu/core/mysql_expression.cpp:317
#4 0x00000000030c5e05 in Tianmu::core::MysqlExpression::TransformTree (this=0x7fd254024e70, root=0x7fd254005840, dir=Tianmu::core::MysqlExpression::TransformDirection::FORWARD)
at /root/work/stonedb-dev-20230605/storage/tianmu/core/mysql_expression.cpp:317
#5 0x00000000030c4ce1 in Tianmu::core::MysqlExpression::MysqlExpression (this=0x7fd254024e70, item=0x7fd254005840, item2varid=std::map with 1 element = {...})
at /root/work/stonedb-dev-20230605/storage/tianmu/core/mysql_expression.cpp:50
#6 0x0000000002d24303 in Tianmu::core::Query::WrapMysqlExpression (this=0x7fd519f6e750, item=0x7fd254005840, tmp_table=..., expr=@0x7fd519f6e1c8: 0x0, in_where=false, aggr_used=false)
at /root/work/stonedb-dev-20230605/storage/tianmu/core/query_compile.cpp:834
#7 0x0000000002d22279 in Tianmu::core::Query::AddFields (this=0x7fd519f6e750, fields=..., tmp_table=..., base_table=..., group_by_clause=false, num_of_added_fields=@0x7fd519f6e4c4: 0,
ignore_minmax=false, aggregation_used=@0x7fd519f6e40f: false) at /root/work/stonedb-dev-20230605/storage/tianmu/core/query_compile.cpp:495
#8 0x0000000002d25efd in Tianmu::core::Query::Compile (this=0x7fd519f6e750, compiled_query=0x7fd519f6e680, selects_list=0x7fd254004bc0, last_distinct=0x0, res_tab=0x0, ignore_limit=false,
left_expr_for_subselect=0x0, oper_for_subselect=0x0, ignore_minmax=false, for_subq_in_where=false) at /root/work/stonedb-dev-20230605/storage/tianmu/core/query_compile.cpp:1210
#9 0x0000000002ceb69c in Tianmu::core::Engine::Execute (this=0x7218d50, thd=0x7fd254000e10, lex=0x7fd254003138, result_output=0x7fd254006a68, unit_for_union=0x0)
at /root/work/stonedb-dev-20230605/storage/tianmu/core/engine_execute.cpp:472
#10 0x0000000002cea825 in Tianmu::core::Engine::HandleSelect (this=0x7218d50, thd=0x7fd254000e10, lex=0x7fd254003138, result=@0x7fd519f6edc8: 0x7fd254006a68, setup_tables_done_option=0,
res=@0x7fd519f6edc4: 0, is_optimize_after_tianmu=@0x7fd519f6edbc: 1, tianmu_free_join=@0x7fd519f6edc0: 1, with_insert=0)
at /root/work/stonedb-dev-20230605/storage/tianmu/core/engine_execute.cpp:243
#11 0x0000000003084b13 in Tianmu::DBHandler::ha_my_tianmu_query (thd=0x7fd254000e10, lex=0x7fd254003138, result_output=@0x7fd519f6edc8: 0x7fd254006a68, setup_tables_done_option=0,
res=@0x7fd519f6edc4: 0, is_optimize_after_tianmu=@0x7fd519f6edbc: 1, tianmu_free_join=@0x7fd519f6edc0: 1, with_insert=0)
at /root/work/stonedb-dev-20230605/storage/tianmu/sql/ha_my_tianmu.cpp:95
#12 0x0000000002427aa8 in execute_sqlcom_select (thd=0x7fd254000e10, all_tables=0x7fd254014a88) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:5204
#13 0x0000000002420e1e in mysql_execute_command (thd=0x7fd254000e10, first_level=true) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:2847
#14 0x0000000002428b0d in mysql_parse (thd=0x7fd254000e10, parser_state=0x7fd519f6ff90) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:5642
#15 0x000000000241db04 in dispatch_command (thd=0x7fd254000e10, com_data=0x7fd519f70730, command=COM_QUERY) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:1495
#16 0x000000000241c945 in do_command (thd=0x7fd254000e10) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:1034
#17 0x000000000254eeb5 in handle_connection (arg=0x965db20) at /root/work/stonedb-dev-20230605/sql/conn_handler/connection_handler_per_thread.cc:313
#18 0x0000000002c1e6f4 in pfs_spawn_thread (arg=0x9490860) at /root/work/stonedb-dev-20230605/storage/perfschema/pfs.cc:2197
#19 0x00007fd569c2b1ca in start_thread () from /lib64/libpthread.so.0
#20 0x00007fd566aa1e73 in clone () from /lib64/libc.so.6
函数实现:
Item_tianmufield *MysqlExpression::GetTianmufieldItem(Item_field *ifield) {
auto key = item2varid->find(ifield);
DEBUG_ASSERT(key != item2varid->end());
auto it = tianmu_fields_cache.find(key->second);
Item_tianmufield *tianmufield = nullptr;
if (it != tianmu_fields_cache.end()) {
tianmufield = *it->second.begin();
tianmufield->varID.push_back(key->second);
} else {
tianmufield = new Item_tianmufield(ifield, key->second);
std::set<Item_tianmufield *> s_tmp;
s_tmp.insert(tianmufield);
tianmu_fields_cache.insert(make_pair(key->second, s_tmp));
}
return (tianmufield);
}
函数目的分析:
- 由于上层逻辑是在执行Query::AddFields和TransformTree, 所以不能从GetTianmufieldItem中获取具体的业务处理相关的信息
- 那么为什么还要在这个函数挂断点分析执行过程呢? 因为这个函数是在处理Item_tianmufield的其中一个过程
Tianmu::core::Query::Preexecute
执行结果的临时表的case里属性的数据:
(gdb) p output_table[0].attrs[0][0]
$27 = (Tianmu::core::TempTable::Attr) {
<Tianmu::core::PhysicalColumn> = {
<Tianmu::core::Column> = {
ct = {
type = Tianmu::common::ColumnType::BIGINT,
unsigned_flag_ = false,
precision = 19,
scale = 0,
internal_size = 8,
display_size = 20,
collation = {
collation = 0x44664e0 <my_charset_bin>,
derivation = DERIVATION_NONE,
repertoire = 3
},
fmt = Tianmu::common::PackFmt::DEFAULT,
flag = std::bitset
}
},
members of Tianmu::core::PhysicalColumn:
_vptr.PhysicalColumn = 0x44057d0 <vtable for Tianmu::core::TempTable::Attr+16>,
is_unique = false,
is_unique_updated = false
},
members of Tianmu::core::TempTable::Attr:
si = {
separator = "",
order = st_order::ORDER_NOT_RELEVANT
},
buffer = 0x0,
no_obj = 0,
no_power = 16,
no_materialized = 0,
page_size = 1,
alias = 0x7fd254024a70 "case when age IS NOT NULL THEN age else 33 end",
mode = Tianmu::common::ColOperation::LISTING,
distinct = false,
term = {
type = Tianmu::common::ColumnType::UNK,
vc = 0x7fd254eb5a30,
cond_value = std::vector of length 0, capacity 0,
cond_numvalue = std::shared_ptr<Tianmu::utils::Hash64> (empty) = {
get() = 0x0
},
vc_id = 0,
is_vc_owner = false,
item = 0x0
},
--Type <RET> for more, q to quit, c to continue without paging--
dim = -2,
orig_precision = 19,
not_complete = true
}
此函数目的:
- 追踪这个函数是从结果出发, 来分析case列相关的属性和数据信息, 倒推case列属性是如何执行的
- 对output_table[0].attrs[0][0]的数据做分析, 有以下奇怪的地方
- mode的值是Tianmu::common::ColOperation::LISTING, 这个是自然属性, 而并不是case这种计算的属性。这种属性会导致直接取值, 而非取值后的case计算
- buffer = 0x0 缓存列的中间结果, 其值为空, 也就是没有拿到数据, 结合mode是LISTING, 其值符合LISTING属性的预期
- 所以可以分析出, case属性的列, 在执行的时候, 是按照LISTING进行处理的, 而并没有做case运算
Query::AddColumnForMysqlExpression
调用堆栈:
#0 Tianmu::core::Query::AddColumnForMysqlExpression (this=0x7fd519f6e750, mysql_expression=0x7fd254024e70, tmp_table=...,
alias=0x7fd2540065c8 "case when age IS NOT NULL THEN age else 33 end", oper=Tianmu::common::ColOperation::LISTING, distinct=false, group_by=false)
at /root/work/stonedb-dev-20230605/storage/tianmu/core/query_compile.cpp:932
#1 0x0000000002d223ec in Tianmu::core::Query::AddFields (this=0x7fd519f6e750, fields=..., tmp_table=..., base_table=..., group_by_clause=false, num_of_added_fields=@0x7fd519f6e4c4: 0,
ignore_minmax=false, aggregation_used=@0x7fd519f6e40f: false) at /root/work/stonedb-dev-20230605/storage/tianmu/core/query_compile.cpp:507
#2 0x0000000002d25efd in Tianmu::core::Query::Compile (this=0x7fd519f6e750, compiled_query=0x7fd519f6e680, selects_list=0x7fd254004bc0, last_distinct=0x0, res_tab=0x0, ignore_limit=false,
left_expr_for_subselect=0x0, oper_for_subselect=0x0, ignore_minmax=false, for_subq_in_where=false) at /root/work/stonedb-dev-20230605/storage/tianmu/core/query_compile.cpp:1210
#3 0x0000000002ceb69c in Tianmu::core::Engine::Execute (this=0x7218d50, thd=0x7fd254000e10, lex=0x7fd254003138, result_output=0x7fd254006a68, unit_for_union=0x0)
at /root/work/stonedb-dev-20230605/storage/tianmu/core/engine_execute.cpp:472
#4 0x0000000002cea825 in Tianmu::core::Engine::HandleSelect (this=0x7218d50, thd=0x7fd254000e10, lex=0x7fd254003138, result=@0x7fd519f6edc8: 0x7fd254006a68, setup_tables_done_option=0,
res=@0x7fd519f6edc4: 0, is_optimize_after_tianmu=@0x7fd519f6edbc: 1, tianmu_free_join=@0x7fd519f6edc0: 1, with_insert=0)
at /root/work/stonedb-dev-20230605/storage/tianmu/core/engine_execute.cpp:243
#5 0x0000000003084b13 in Tianmu::DBHandler::ha_my_tianmu_query (thd=0x7fd254000e10, lex=0x7fd254003138, result_output=@0x7fd519f6edc8: 0x7fd254006a68, setup_tables_done_option=0,
res=@0x7fd519f6edc4: 0, is_optimize_after_tianmu=@0x7fd519f6edbc: 1, tianmu_free_join=@0x7fd519f6edc0: 1, with_insert=0)
at /root/work/stonedb-dev-20230605/storage/tianmu/sql/ha_my_tianmu.cpp:95
#6 0x0000000002427aa8 in execute_sqlcom_select (thd=0x7fd254000e10, all_tables=0x7fd254014a88) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:5204
#7 0x0000000002420e1e in mysql_execute_command (thd=0x7fd254000e10, first_level=true) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:2847
#8 0x0000000002428b0d in mysql_parse (thd=0x7fd254000e10, parser_state=0x7fd519f6ff90) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:5642
#9 0x000000000241db04 in dispatch_command (thd=0x7fd254000e10, com_data=0x7fd519f70730, command=COM_QUERY) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:1495
#10 0x000000000241c945 in do_command (thd=0x7fd254000e10) at /root/work/stonedb-dev-20230605/sql/sql_parse.cc:1034
#11 0x000000000254eeb5 in handle_connection (arg=0x965db20) at /root/work/stonedb-dev-20230605/sql/conn_handler/connection_handler_per_thread.cc:313
#12 0x0000000002c1e6f4 in pfs_spawn_thread (arg=0x9490860) at /root/work/stonedb-dev-20230605/storage/perfschema/pfs.cc:2197
#13 0x00007fd569c2b1ca in start_thread () from /lib64/libpthread.so.0
#14 0x00007fd566aa1e73 in clone () from /lib64/libc.so.6
函数实现:
Query::AddFields
QueryRouteTo Query::AddFields(List<Item> &fields, TabID const &tmp_table, TabID const &base_table,
bool const group_by_clause, int &num_of_added_fields, bool ignore_minmax,
bool &aggregation_used) {
List_iterator_fast<Item> li(fields);
Item *item;
int added = 0;
item = li++;
while (item) {
WrapStatus ws;
common::ColOperation oper;
bool distinct;
if (QueryRouteTo::kToMySQL == OperationUnmysterify(item, oper, distinct, group_by_clause))
return QueryRouteTo::kToMySQL;
if (IsAggregationItem(item))
aggregation_used = true;
// in case of transformed subquery sometimes we need to revert back
// transformation to MIN/MAX
if (ignore_minmax && (oper == common::ColOperation::MIN || oper == common::ColOperation::MAX))
oper = common::ColOperation::LISTING;
// select PHYSICAL COLUMN or AGGREGATION over PHYSICAL COLUMN
if ((IsFieldItem(item) || IsAggregationOverFieldItem(item)) &&
(IsLocalColumn(item, tmp_table) || (!base_table.IsNullID() && IsLocalColumn(item, base_table))))
AddColumnForPhysColumn(item, tmp_table, base_table, oper, distinct, false, item->item_name.ptr());
// REF to FIELD_ITEM
else if (item->type() == Item::REF_ITEM) {
item = UnRef(item);
continue;
}
// if ((UnRef(item)->type() == Item_tianmufield::enumTIANMUFiledItem::TIANMUFIELD_ITEM ||
// UnRef(item)->type() == Item_tianmufield::FIELD_ITEM) &&
// IsLocalColumn(UnRef(item), tmp_table))
// AddColumnForPhysColumn(UnRef(item), tmp_table, oper, distinct, false, false);
// else {
// //
// }
else if (IsAggregationItem(item) && (((Item_sum *)item)->get_arg(0))->type() == Item::REF_ITEM &&
(UnRef(((Item_sum *)item)->get_arg(0))->type() == Item_tianmufield::get_tianmuitem_type() ||
(UnRef(((Item_sum *)item)->get_arg(0))->type() == Item_tianmufield::FIELD_ITEM)) &&
IsLocalColumn(UnRef(((Item_sum *)item)->get_arg(0)), tmp_table))
// AGGR on REF to FIELD_ITEM
AddColumnForPhysColumn(UnRef(((Item_sum *)item)->get_arg(0)), tmp_table, TabID(), oper, distinct, false,
item->item_name.ptr());
else if (IsAggregationItem(item)) {
// select AGGREGATION over EXPRESSION
Item_sum *item_sum = (Item_sum *)item;
if (item_sum->get_arg_count() > 1 || HasAggregation(item_sum->get_arg(0)))
return QueryRouteTo::kToMySQL;
if (IsCountStar(item_sum)) { // count(*) doesn't need any virtual column
AttrID at;
cq->AddColumn(at, tmp_table, CQTerm(), oper, item_sum->item_name.ptr(), false);
field_alias2num[TabIDColAlias(tmp_table.n, item_sum->item_name.ptr())] = at.n;
} else {
MysqlExpression *expr;
ws = WrapMysqlExpression(item_sum->get_arg(0), tmp_table, expr, false, false);
if (ws == WrapStatus::FAILURE)
return QueryRouteTo::kToMySQL;
AddColumnForMysqlExpression(expr, tmp_table,
ignore_minmax ? item_sum->get_arg(0)->item_name.ptr() : item_sum->item_name.ptr(),
oper, distinct);
}
} else if (item->type() == Item::SUBSELECT_ITEM) {
CQTerm term;
AttrID at;
if (Item2CQTerm(item, term, tmp_table,
/*group_by_clause ? HAVING_FILTER :*/ CondType::WHERE_COND) == QueryRouteTo::kToMySQL)
return QueryRouteTo::kToMySQL;
cq->AddColumn(at, tmp_table, term, common::ColOperation::LISTING, item->item_name.ptr(), distinct);
field_alias2num[TabIDColAlias(tmp_table.n, item->item_name.ptr())] = at.n;
} else {
// select EXPRESSION
if (HasAggregation(item)) {
oper = common::ColOperation::DELAYED;
aggregation_used = true;
}
MysqlExpression *expr(nullptr);
ws = WrapMysqlExpression(item, tmp_table, expr, false, oper == common::ColOperation::DELAYED);
if (ws == WrapStatus::FAILURE)
return QueryRouteTo::kToMySQL;
if (!item->item_name.ptr()) {
Item_func_conv_charset *item_conv = dynamic_cast<Item_func_conv_charset *>(item);
if (item_conv) {
Item **ifunc_args = item_conv->arguments();
AddColumnForMysqlExpression(expr, tmp_table, ifunc_args[0]->item_name.ptr(), oper, distinct);
} else {
AddColumnForMysqlExpression(expr, tmp_table, item->item_name.ptr(), oper, distinct);
}
} else
AddColumnForMysqlExpression(expr, tmp_table, item->item_name.ptr(), oper, distinct);
}
added++;
item = li++;
}
num_of_added_fields = added;
return QueryRouteTo::kToTianmu;
}
(gdb) p item
$30 = (Item_func_case *) 0x7fd254005840
此函数目的:
- case的列属性构建tianmu的查询序列, mode设置为LISTING导致了case运算的缺失, 那么就需要分析构建查询序列的列属性的过程
- 分析该函数有这么几个目的:
- 尝试将tianmu的列属性与mysql/sql的列属性保持一致, 补充确实的case运算
- 如果tianmu引擎无法计算该属性, 直接拿到item的信息进行处理