MySQL对于datetime 源码分析

http://tsecer.blog.163.com/blog/static/150181720160117355684/

一、时间比较的语法分析

在mysql中,通常时间是一个必不可少的类型,而这种类型的一个特殊地方就在于它的比较函数。其实,即使对于字符串的比较函数和对于int的比较也是完全不同的,但是这个差别并不想以datetime保存的时间这么具有视觉的震撼性和表现的这么明显。这里还有一个问题在于,当mysql的yacc在进行语法分析的时候,这个时候语法分析器并没有读取一个field中的结构,或者说并没有对fiedl进行fix操作,所以当parser看到一个 field > "2016-01-11 00:00:00"这样的字符串的时候,它并不知道这个地方是要进行一个字符串的比较还是一个时间的比较,而需要在对于一个sql的表达式的各个field进行fix之后才能够找到这个对应的field的类型,并进而进行类型的判断,这里主要说明的是一个比较函数延迟设置的问题。

由于mysql有内置的mysql.event表格包含了有datetime类型的字段,所以使用这个内置的类型进行比较说明是比较合适的:

select SQL_NO_CACHE * from event where created > "2016-01-10 00:00:00";

在sql_yacc.yy的语法分析函数中,此时使用不同的creator创建了比较的item:

bool_pri:

bool_pri IS NULL_SYM %prec IS

{

$$= new (YYTHD->mem_root) Item_func_isnull($1);

if ($$ == NULL)

MYSQL_YYABORT;

}

……

| bool_pri comp_op predicate %prec EQ

{

$$= (*$2)(0)->create($1,$3);

if ($$ == NULL)

MYSQL_YYABORT;

}

comp_op:

EQ     { $$ = &comp_eq_creator; }

| GE     { $$ = &comp_ge_creator; }

| GT_SYM { $$ = &comp_gt_creator; }

| LE     { $$ = &comp_le_creator; }

| LT     { $$ = &comp_lt_creator; }

| NE     { $$ = &comp_ne_creator; }

;

Item_bool_func2* Ge_creator::create(Item *a, Item *b) const

{

return new Item_func_ge(a, b);

}

二、具体比较方法的确定

setup_conds-->>Item_bool_func2::fix_length_and_dec-->>Item_bool_func2::set_cmp_func-->>Arg_comparator::set_cmp_func-->>

可以看到的是,在进入函数之后,优先判断的datetime类型的比较类型,这个优先级最高,如果比较的两方一个是datetime类型,而另一个是string类型,则会执行对string向时间的转换。

int Arg_comparator::set_cmp_func(Item_result_field *owner_arg,

Item **a1, Item **a2,

Item_result type)

{

enum enum_date_cmp_type cmp_type;

ulonglong const_value= (ulonglong)-1;

thd= current_thd;

owner= owner_arg;

set_null= set_null && owner_arg;

a= a1;

b= a2;

thd= current_thd;

if ((cmp_type= can_compare_as_dates(*a, *b, &const_value)))

{

a_type= (*a)->field_type();

b_type= (*b)->field_type();

a_cache= 0;

b_cache= 0;

if (const_value != (ulonglong)-1)

{

/*

cache_converted_constant can‘t be used here because it can‘t

correctly convert a DATETIME value from string to int representation.

*/

Item_cache_int *cache= new Item_cache_int(MYSQL_TYPE_DATETIME);

/* Mark the cache as non-const to prevent re-caching. */

cache->set_used_tables(1);

if (!(*a)->is_datetime())

{

cache->store((*a), const_value);

a_cache= cache;

a= (Item **)&a_cache;

}

else

{

cache->store((*b), const_value);

b_cache= cache;

b= (Item **)&b_cache;

}

}

is_nulls_eq= is_owner_equal_func();

func= &Arg_comparator::compare_datetime;

get_value_a_func= &get_datetime_value;

get_value_b_func= &get_datetime_value;

return 0;

}

else if (type == STRING_RESULT && (*a)->field_type() == MYSQL_TYPE_TIME &&

(*b)->field_type() == MYSQL_TYPE_TIME)

{

/* Compare TIME values as integers. */

a_cache= 0;

b_cache= 0;

is_nulls_eq= is_owner_equal_func();

func= &Arg_comparator::compare_datetime;

get_value_a_func= &get_time_value;

get_value_b_func= &get_time_value;

return 0;

}

else if (type == STRING_RESULT &&

(*a)->result_type() == STRING_RESULT &&

(*b)->result_type() == STRING_RESULT)

{

DTCollation coll;

coll.set((*a)->collation.collation);

if (agg_item_set_converter(coll, owner->func_name(),

b, 1, MY_COLL_CMP_CONV, 1))

return 1;

}

else if (try_year_cmp_func(type))

return 0;

a= cache_converted_constant(thd, a, &a_cache, type);

b= cache_converted_constant(thd, b, &b_cache, type);

return set_compare_func(owner_arg, type);

}

如果datefield的另一端不是string类型,此时比较进入另一份分支,按照int类型来比较:

void Item_bool_func2::fix_length_and_dec()

……

if (!thd->lex->is_ps_or_view_context_analysis())

{

if (args[0]->real_item()->type() == FIELD_ITEM)

{

Item_field *field_item= (Item_field*) (args[0]->real_item());

if (field_item->field->can_be_compared_as_longlong() &&

!(field_item->is_datetime() &&

args[1]->result_type() == STRING_RESULT))

{

if (convert_constant_item(thd, field_item, &args[1]))

{

cmp.set_cmp_func(this, tmp_arg, tmp_arg+1,

INT_RESULT); // Works for all types.

args[0]->cmp_context= args[1]->cmp_context= INT_RESULT;

return;

}

}

}

三、datetime类型的解析

enum enum_mysql_timestamp_type

str_to_datetime(const char *str, uint length, MYSQL_TIME *l_time,

uint flags, int *was_cut)

{

……

for (i = start_loop;

i < MAX_DATE_PARTS-1 && str != end &&

my_isdigit(&my_charset_latin1,*str);

i++)

{

……

while (str != end &&

(my_ispunct(&my_charset_latin1,*str) ||

my_isspace(&my_charset_latin1,*str)))

{

if (my_isspace(&my_charset_latin1,*str))

{

if (!(allow_space & (1 << i)))

{

*was_cut= 1;

DBUG_RETURN(MYSQL_TIMESTAMP_NONE);

}

found_space= 1;

}

str++;

found_delimitier= 1;                      /* Should be a ‘normal‘ date */

}

这里可以看到,它对各个字段的分隔符并没有和我们常见的所谓“YYYY-MM-DD HH:MM:SS”,而是任意的一个分隔符都可以,一我们默认的latin字符集为例,可以看到大量的字符都可以作为分隔符,下面所有和0x10逻辑与之后非零的字符都可以具有分隔符的功能,MysQL对于这个没有任何要求,也就是它并不挑食mysql-5.1.61\strings\ctype-latin1.c:

static uchar ctype_latin1[] = {

0,

32, 32, 32, 32, 32, 32, 32, 32, 32, 40, 40, 40, 40, 40, 32, 32,

32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,

72, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,

132,132,132,132,132,132,132,132,132,132, 16, 16, 16, 16, 16, 16,

16,129,129,129,129,129,129,  1,  1,  1,  1,  1,  1,  1,  1,  1,

1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1, 16, 16, 16, 16, 16,

16,130,130,130,130,130,130,  2,  2,  2,  2,  2,  2,  2,  2,  2,

2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2, 16, 16, 16, 16, 32,

16,  0, 16,  2, 16, 16, 16, 16, 16, 16,  1, 16,  1,  0,  1,  0,

0, 16, 16, 16, 16, 16, 16, 16, 16, 16,  2, 16,  2,  0,  2,  1,

72, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,

16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,

1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,

1,  1,  1,  1,  1,  1,  1, 16,  1,  1,  1,  1,  1,  1,  1,  2,

2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,

2,  2,  2,  2,  2,  2,  2, 16,  2,  2,  2,  2,  2,  2,  2,  2

};

mysql> select date( "2016|01!10 00%00%00"),  time( "2016-01-10 11%22$33");

+------------------------------+------------------------------+

| date( "2016|01!10 00%00%00") | time( "2016-01-10 11%22$33") |

+------------------------------+------------------------------+

| 2016-01-10                   | 11:22:33                     |

+------------------------------+------------------------------+

1 row in set (0.00 sec)

mysql>

四、datetime的存储

这个非常有意思,跳出了我们常见的utc保存的常规思路,那么为什么它就可以使用这么简洁直观的方式呢?究其原因,就是有空间就是任性,现在的Unix时间通常是按照long存储的,但是早期的long类型都是32字节,所以即使从1970年开始,到2038年就会产生负数,所以必须进行压缩,也就是转换为从1970开始的秒数,现在使用更长的存储空间来存储,所以这个方法就是直接使用这种格式存储就可以了

/* Convert time value to integer in YYYYMMDDHHMMSS format */

ulonglong TIME_to_ulonglong_datetime(const MYSQL_TIME *my_time)

{

return ((ulonglong) (my_time->year * 10000UL +

my_time->month * 100UL +

my_time->day) * ULL(1000000) +

(ulonglong) (my_time->hour * 10000UL +

my_time->minute * 100UL +

my_time->second));

}

mysql> select cast( cast(‘2007-12-25‘ as DATETIME)  as UNSIGNED INT);

+--------------------------------------------------------+

| cast( cast(‘2007-12-25‘ as DATETIME)  as UNSIGNED INT) |

+--------------------------------------------------------+

|                                         20071225000000 |

+--------------------------------------------------------+

1 row in set (0.01 sec)

mysql>

五、比较中使用哪种类型的选择

和很多的非强制类型一样,mysql允许在多种不同的类型之间进行自动转换,这里就涉及到选择结构中的数据类型是向哪一个靠拢的问题,当然如果说两个类型都是相同的话,那么皆大欢喜。问题在于经常会遇到类型不一致的两个变量进行操作,此时就需要判断结果到底是什么类型,这里需要在具体的环境下进行具体的分析,但是大致来说有一个大致的原则在一个函数中体现mysql-5.1.61\sql\item.cc:

Item_result item_cmp_type(Item_result a,Item_result b)

{

if (a == STRING_RESULT && b == STRING_RESULT)

return STRING_RESULT;

if (a == INT_RESULT && b == INT_RESULT)

return INT_RESULT;

else if (a == ROW_RESULT || b == ROW_RESULT)

return ROW_RESULT;

if ((a == INT_RESULT || a == DECIMAL_RESULT) &&

(b == INT_RESULT || b == DECIMAL_RESULT))

return DECIMAL_RESULT;

return REAL_RESULT;

}

这里可以看到的是,如果两个都是string,那么结果就是string,但是如果两者不一致,只要有任何一个是一个int类型,那么尽量向int类型靠拢。

六、总结

总起来说,这里讲到的问题在实际中的意义并不大,可以说没有什么意义。只是说为了这个问题遇到的情况比较多,或者说这里看到的问题比较常见,所以希望大家如果从这里能够看到一些其它的更为需要的流程来说,就非常有意义了。

时间: 2024-10-11 07:59:54

MySQL对于datetime 源码分析的相关文章

MySQL系列:innodb源码分析之redo log恢复

在上一篇<innodb源码分析之重做日志结构>中我们知道redo log的基本结构和日志写入步骤,那么redo log是怎么进行数据恢复的呢?在什么时候进行redo log的日志推演呢?redo log的推演只有在数据库异常或者关闭后,数据库重新启动时会进行日志推演,将数据库状态恢复到关闭前的状态.那么这个过程是怎么进行的呢?以下我们逐步来解析. 1.recv_sys_t结构 innodb在MySQL启动的时候,会对重做日志文件进行日志重做,重做日志是通过一个recv_sys_t的结构来进行数

mysql jdbc源码分析片段 和 Tomcat&#39;s JDBC Pool

32) Tomcat's JDBC Pool Tomcat jdbc pool的使用仅需2个jar包,分别为tomcat-jdbc.jar和tomcat-juli.jar,这两个jar包都可以在tomcat7中找到,tomcat-jdbc.jar在tomcat的lib目录下,tomcat-juli.jar在bin目录下. http://svn.apache.org/viewvc/tomcat/trunk/modules/jdbc-pool/ org.apache.tomcat.jdbc.pool

【MyBatis源码分析】环境准备

前言 之前一段时间写了[Spring源码分析]系列的文章,感觉对Spring的原理及使用各方面都掌握了不少,趁热打铁,开始下一个系列的文章[MyBatis源码分析],在[MyBatis源码分析]文章的基础之上,可以继续分析数据库连接池.Spring整合MyBatis源码.Spring事物管理tx等等. [MyBatis源码分析]整个文章结构相较[Spring源码分析]稍微改一改,后者会在每一部分源码分析的开头列出要分析的源码的实例,比如: 分析Bean流程加载,就会先写Bean的代码示例及xml

Mybatis源码分析一(SqlsessionFactory及源码整体结构)

搞java的想提高自己的姿势水平,想拿高工资,对常用开源框架的深入了解是必不可少的,想深入了解源码分析更是必不可少的,今天我开始对mybatis的源码进行分析,并做点记录以备查验.开源框架研究,文档的获取建议去读官方的文档和例子,这样获得的知识成体系,成体系的知识被你掌握了,你就可以说你精通它了.好了,开始吧. 上面说道要看官方的文档,那么就得找到官方网站什么的对吧?这里给几个网站都是不错的: Myabtis官网:http://www.mybatis.org/ github地址:https://

JFinal 源码分析 [DB+ActiveRecord]

我记得以前有人跟我说,“面试的时候要看spring的源码,要看ioc.aop的源码"那为什么要看这些开源框架的源码呢,其实很多人都是"应急式"的去读,就像读一篇文章一下,用最快的速度把文章从头到尾读一遍,那结果就是当你读完它,你也不清楚它讲了一个什么故事,想表达什么. 一个优秀的架构的源码我认为就好像一本名著一样,你的“文学”水平越高,你就越能读出作者设计的精妙之处.一篇源码在你不同水平的时候,能读出不同的东西,因此,我觉得优秀的框架的源码是经久不衰的,反复读多少次都不嫌多,

Caching-缓存架构与源码分析

Caching-缓存架构与源码分析 首先奉献caching的开源地址[微软源码] 1.工程架构 为了提高程序效率,我们经常将一些不频繁修改,但是使用了还很大的数据进行缓存.尤其是互联网产品,缓存可以说是提升效率优化第一利器.微软为我们实现了俩种缓存方式:内存缓存.分布式缓存.个人理解如果缓存在前端电脑内存的缓存叫做内存缓存,如果缓存在其它设备上,那么叫做分布式缓存. 俩种缓存方式的优缺点 我开发程序经历过三个时间点,开始的时候从来不使用缓存,之后将数据缓存在内存中,最后使用分布式缓存.内存缓存的

【MyBatis】MyBatis Tomcat JNDI原理及源码分析

一. Tomcat JNDI JNDI(java nameing and drectory interface),是一组在Java应用中访问命名和服务的API,所谓命名服务,即将对象和名称联系起来,使得可以通过名称访问并获取对象. 简单原理介绍:点击访问 tomcat已经集成该服务(内置并默认使用DBCP连接池),简单来说就是键值对的mapping,而且在tomcat服务器启动的首页configuration中就已经有完成的示例代码.要想使用tomcat的JNDI服务,只需要导入相关的jar包,

MyCat源码分析系列之——SQL下发

更多MyCat源码分析,请戳MyCat源码分析系列 SQL下发 SQL下发指的是MyCat将解析并改造完成的SQL语句依次发送至相应的MySQL节点(datanode)的过程,该执行过程由NonBlockingSession.execute()触发: public void execute(RouteResultset rrs, int type) { // clear prev execute resources clearHandlesResources(); if (LOGGER.isDe

MyBatis源码分析-SQL语句执行的完整流程

MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录.如何新建MyBatis源码工程请点击MyBatis源码分析-IDEA新建MyBatis源码工程. MyBatis框架主要完成的是以下2件事情: 根据JD