標籤:
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_typestr_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類型靠攏。
六、總結總起來說,這裡講到的問題在實際中的意義並不大,可以說沒有什麼意義。只是說為了這個問題遇到的情況比較多,或者說這裡看到的問題比較常見,所以希望大家如果從這裡能夠看到一些其它的更為需要的流程來說,就非常有意義了。
MySQL對於datetime 源碼分析