MySQL对于datetime 源码分析

时间:2021-09-26 07:23:53

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