数学建模社区-数学中国

标题: MySql轻松入门系列————第一站 从源码角度轻松认识mysql整体框架图 [打印本页]

作者: zhangtt123    时间: 2020-6-3 10:36
标题: MySql轻松入门系列————第一站 从源码角度轻松认识mysql整体框架图
一:背景
  H+ a* t* z5 z/ P  n1. 讲故事  v- q1 {  `9 L/ q1 N% T0 b
最近看各大技术社区,不管是知乎,掘金,博客园,csdn基本上看不到有小伙伴分享sqlserver类的文章,看样子这些年sqlserver没落了,已经后继无人了,再写sqlserver是不可能再写了,这辈子都不会写了,只能靠技术输出mysql维持生活这样子。
. |8 k: P+ q2 S6 N1 a# C" T( D/ K
二:了解架构图8 p( a+ b3 ?- W$ Y/ b
mysql最大的好处就是开源, 手握百万源码,有什么问题搞不定呢? 这一点要比sqlserver爽多了,不用再dbcc捣来捣去。
6 S$ X* N4 A  [( h, U/ Q' ?8 y$ ^+ a
1. 从架构图入手# [! C4 F, X- x
大家都知道做/装修房子都要有一张图纸,其实软件也是一样,只要有了这么一张图纸,大方向就定下来了,再深入到细节也不会乱了方向,然后给大家看一下我自己画的架构图,画的不对请轻拍。& v# H7 Z+ o& K8 [8 b; n

) V. r/ [1 P& [1 O& Q- r
& s; [+ L8 T" Y' o: D- D3 l
4 j$ u0 T5 _1 e) {) h3 C* Y. Q0 R其实SqlServer,Oracle,MySql架构都大同小异,MySql的鲜明特点就是存储引擎做成了插拔式,这就牛逼了,现行最常用的是InnoDB,这就让我有了一个想法,有一套业务准备用 InMemory 模式跑一下,厉害了~
5 k8 k5 s5 @8 W2 s5 _3 v$ s, C% J3 Q1 Y9 K  ~5 w+ z! x' ~
2. 功能点介绍
* B% D0 g$ n. P( D6 N% sMySql其实就两大块,一块是MySql Server层,一块就是Storage Engines层。& ^- s5 X$ e8 |+ F- v2 F  o

6 K; M3 p4 s8 F<1> Client
7 P: I  T' r& K# c不同语言的sdk遵守mysql协议就可以与mysqld进行互通。# U/ w6 R! |, X$ X; _' z
0 n& l. O8 ]3 `
<2> Connection/Thread Pool
  x7 M* \+ z/ `/ ?1 I" SMySql使用C++编写,Connection是非常宝贵的,在初始化的时候维护一个池。
  k0 ?3 b1 O3 t9 [& |# c2 q4 Q  w# A' b! N2 c7 n
<3> SqlInterface,Parse,Optimizer,Cache2 u! ]( q- T: k! z3 L  }0 J
对sql处理,解析,优化,缓存等处理和过滤模块,了解了解即可。
; v* v* d+ l' \1 O8 N6 b9 Q7 V/ J. A) L5 d# S
<4> Storage Engines
+ B+ S& Y3 l4 a- _0 _0 K6 W负责存储的模块,官方,第三方,甚至是你自己都可以自定义实现这个数据存储,这就把生态做起来了,&#128046;&#128067;。: ~, b" K7 H1 @5 G

6 a) s% Q8 T4 G! s/ D) }* E' G- D, I三: 源码分析
( y. v. r  D- m% U关于怎么去下载mysql源码,这里就不说了,大家自己去官网捣鼓捣鼓哈,本系列使用经典的 mysql 5.7.14版本。1 Y7 N3 ^0 i* m2 w6 m2 F

  j, E7 l. [$ H% C& X1. 了解mysql是如何启动监听的
% H, p% L! J3 J6 a4 Y手握百万行源码,怎么找入口函数呢??? &#128513;&#128513;&#128513;,其实很简单,在mysqld进程上生成一个dump文件,然后看它的托管堆不就好啦。。。
+ X; |( h5 u3 c  r( x* b9 g9 N8 F1 ^5 H2 I  v' d9 k' C

& j: R' E0 X; [% t2 h/ ], S& h* i. c, n; v2 c
从图中可以看到,入口函数就是 mysqld!mysqld_main+0x227 中的 mysqld_main, 接下来就可以在源码中全文检索下。( W( i" j# b* J7 W  l2 ?7 V5 y7 z

* s5 S) q8 }7 }, b, o<1> mysqld_main 入口函数 => sql/main.cc3 T  X" ~  V5 ~4 @- |

! J6 u0 t! S8 L( P
" v; f% `/ ~1 {! P, C4 f: ]extern int mysqld_main(int argc, char **argv);
$ ^4 f; w: J/ g( W3 k
% g% m% l* Z% K7 Sint main(int argc, char **argv)* }- M9 Y/ j4 _+ r
{
3 [9 d8 B5 l6 q/ x  return mysqld_main(argc, argv);
8 m* O( l: b, H  k}2 a5 d1 B  p: ]4 O0 F$ ~! @8 S
6 M; x- j/ [4 f2 z, V" r
: r; i/ Z. Y) |5 S6 h5 k, G
这里大家可以用visualstudio打开C++源码,使用查看定义功能,非常好用。2 ]/ P  X) a6 A* [* E) e

& R+ o5 k+ A! ~; n9 W  R<2> 创建监听
) [' p7 |  C$ M' V/ w4 @4 {8 l/ L: ]- Q* A
. w! w; P! L( _3 N1 j
int mysqld_main(int argc, char **argv)7 I' C+ X  s0 ]# Y: u# v5 B
{* ?9 r! u8 [4 v8 K4 W* g
    //创建服务监听线程4 A1 |$ ?; N) I, g
    handle_connections_sockets();
0 B. c% x! t$ b4 ]8 c+ ]/ L}
8 M& F8 C  E8 a0 \8 \9 T+ Z+ O  y6 _- e4 V
void handle_connections_sockets()) y% e, Y1 u5 ?$ @9 O  Z
{
4 D% `" R7 Y/ z9 ]     //监听连接1 c/ e0 Z! I0 @8 h
     new_sock= mysql_socket_accept(key_socket_client_connection, sock,
6 M1 ~; f! {5 C* }                                    (struct sockaddr *)(&cAddr), &length);9 `2 K, t) {! z0 Z$ v3 F
3 `; K7 W' R+ Y  t3 r
    if (mysql_socket_getfd(sock) == mysql_socket_getfd(unix_sock))5 e2 z7 k' V6 i: s
      thd->security_ctx->set_host((char*) my_localhost);* r; J. [. ?! G( L- k4 U* W
) |- L* m+ }+ ~4 A* Q# b+ y6 q( B
    //创建连接
; g! `; W8 \2 B4 Z; C7 z    create_new_thread(thd);
4 |$ p4 p/ F8 D) w6 c/ L}% l3 M% u! n% e2 h) Q
: p1 c. x0 H, l5 C# {
//创建新线程处理处理用户连接/ G1 o  ~( S( m& _6 @
static void create_new_thread(THD *thd){, H: {: N+ {1 O7 e
; W- K0 v# K" v8 i% U" X3 O* j& G
   thd->thread_id= thd->variables.pseudo_thread_id= thread_id++;
- \: j$ g- v7 }
! b+ X/ q/ O, Q) Z   //线程进了线程调度器
% w% a) h2 j" R+ s5 V; w  q, H   MYSQL_CALLBACK(thread_scheduler, add_connection, (thd));   
5 b- y7 m! d* l: ?. ]* |, \}0 D+ A. T# S0 m" N3 {* D8 s

& i, B' ^+ F0 h- G6 R0 m/ `9 w( f- J& V  D
至此mysql就开启了一个线程对 3306 端口进行监控,等待客户端请求触发 add_connection 回调。; A% ]( q! L5 ]* N
1 v$ l9 k+ T6 ^8 P' d7 g0 H: M

7 K7 S% v; d8 p) S2 A2. 理解mysql是如何处理sql请求
7 v% i, q7 F! I这里我以Insert操作为例稍微解剖下处理流程:
0 x+ A: ?! a1 v* v3 l) O3 B
  g/ S/ R& N) p当用户有请求sql过来之后,就会触发 thread_scheduler的回调函数add_connection。2 w$ L' A5 y! y% S
9 K8 ~& U( x  j1 }

' t- P) S; G6 ?* mstatic scheduler_functions one_thread_per_connection_scheduler_functions=4 m; k2 q, \# f) x4 l' e/ E& `' ?
{
/ e+ `2 ~- I; s4 g& }2 m6 Z+ P  0,                                     // max_threads7 W0 G- U' T0 S* k1 x+ S% B* N5 V
  NULL,                                  // init( H6 s% S, i1 x5 m  l# u9 u
  init_new_connection_handler_thread,    // init_new_connection_thread
; n) j0 q- p! M; O! u# N# b" }  create_thread_to_handle_connection,    // add_connection! T0 X, N5 X, B9 q
  NULL,                                  // thd_wait_begin9 `" b6 U; p: }
  NULL,                                  // thd_wait_end
; s9 J7 \. o; ^6 o  NULL,                                  // post_kill_notification0 q8 M% R; z  X  S1 T6 W1 K5 D
  one_thread_per_connection_end,         // end_thread4 s0 C9 e  l& N2 S* S
  NULL,                                  // end  k0 ^6 @  a  l* D) P2 x7 d: Y
};# P0 m  e& G3 U. ]4 Q. {4 v/ M' n
( c3 o. K1 F' F* u; [' M4 \$ K

; \) W3 `. R. x" `从 scheduler_functions 中可以看到,add_connection 对应了 create_thread_to_handle_connection,也就是请求来了会触发这个函数,从名字也可以看出,用一个线程处理一个用户连接。
8 k* m6 _" L" p. }/ c; t" U: f/ @' L/ k* u2 r( X0 P
<1> 客户端请求被 create_thread_to_handle_connection 接管及调用栈追踪% N8 Y( b$ ~) H7 X+ n
5 }% l* x' e% P, ~9 u. i- t* Y
void create_thread_to_handle_connection(THD *thd)3 ?2 ~- E3 l; G: V: }& T" R
{5 F/ r- ?. I3 T! b* Q0 {9 u8 q
     if ((error= mysql_thread_create(key_thread_one_connection, &thd->real_id, &connection_attrib,8 n% K. T0 Y% f- b7 Y' L, ~  _1 x
                                     handle_one_connection,(void*) thd))){}- _- b  v+ n; i7 B: M0 I0 r6 l3 h
}
; p% S' g9 ?& V+ L0 r5 R, O  ~//触发回调函数  handle_one_connection9 J9 R) z/ P) c  Z" \* F
pthread_handler_t handle_one_connection(void *arg)" u( R; e8 K) G7 @& |8 }# R
{' \. e! D3 x" d& ]  c% `4 I5 a* m
     do_handle_one_connection(thd);4 K: c3 Q0 _! D& D! }% C# d5 y
}* t' C4 {& Y: H, s0 m3 f5 ]* a
//继续处理% H. u9 v% z$ p/ a" h2 W
void do_handle_one_connection(THD *thd_arg){
% ^  q- E; G" C2 ]* W. b- R    while (thd_is_connection_alive(thd))
( B5 i/ u: e& b& i9 @/ k    {+ G0 i, K/ d* V" X/ G" |
      mysql_audit_release(thd);
5 f7 c4 @' o7 Y4 l0 a2 }) u8 \! X      if (do_command(thd))  break;  //这里的 do_command 继续处理
1 ~4 R, _, ]  N7 P7 E    }
# b! L% `5 V) ~7 K4 L+ {% m}' }; h# Q. @; f% Q" e- P
//继续分发
5 k( Y. d! G7 A5 j% l5 w: P6 n! hbool do_command(THD *thd)$ Y) w& o/ O4 J; v7 h
{
- U9 D9 M( U0 j; c    return_value= dispatch_command(command, thd, packet+1, (uint) (packet_length-1));8 G1 k9 h( N) \. a, I1 G
}0 [) \4 [- g1 m. C, f/ k9 ~
bool dispatch_command(enum enum_server_command command, THD *thd, char* packet, uint packet_length)2 o2 o' h9 R" X8 [2 I" @
{
2 N& g* t6 z' Y. g& s      switch (command) {: H5 n  ?2 H  p
         case COM_INIT_DB: ....  break;3 t  `- w1 b) m( o0 x4 _+ y8 `; }
         ...
% N1 a$ X* u( O4 ]         case COM_QUERY:   //查询语句:  insert xxxx
) A1 k7 }  X% @9 a" t/ q% R; U             mysql_parse(thd, thd->query(), thd->query_length(), &parser_state);  //sql解析
/ S( t6 V, K9 |. g) v. |3 H5 j           break;) c: P7 M; d" |6 ^+ i
      }
, i+ f( W3 l3 d9 n5 Y0 r}
4 i* J3 ?* i2 H: _//sql解析模块2 J& S  Z3 }8 @
void mysql_parse(THD *thd, char *rawbuf, uint length, Parser_state *parser_state)8 D9 F, [7 U( X* U
{
3 ^* _8 _& C6 f" i' U      error= mysql_execute_command(thd);
' X  N. a' Y* Q) m' ?- k: f}* m* Q6 k% U( q2 s8 d1 O
* u  v9 M; _, q( G: j

# s3 M3 j' `# S. Q6 t<2> 到这里它的Parse,Optimizer,Cache都追完了,接下来看sql的CURD类型,继续追。。。6 b2 R) x) C9 D$ l$ v& k

3 q& {& K  C: |) B" [. _//继续执行
9 j' h2 E) W$ a8 Yint mysql_execute_command(THD *thd)
8 D: ?4 ]9 w/ i9 m{
2 C9 g2 ~% J+ Y% l& |6 [2 c  switch (lex->sql_command)
3 K  g& {1 l" z+ S. F9 H  {
/ c) ?6 ?: s; e. a      case SQLCOM_SELECT:  res= execute_sqlcom_select(thd, all_tables);  break;$ U0 x0 W2 D" t; i2 [% Z5 U0 o
+ ?% h1 [& p( }9 U- [; G% z
      //这个 insert 就是我要追的$ p4 B/ j$ p1 [& J1 Z+ C6 u$ [
      case SQLCOM_INSERT:   res= mysql_insert(thd, all_tables, lex->field_list, lex->many_values,
( l9 G5 p! n4 ]5 l2 U. V5 I                                              lex->update_list, lex->value_list,
1 s" O) J* A" V                                              lex->duplicates, lex->ignore);
# m  }( o% }& J+ u8 @0 E0 f- f2 q  }$ t5 I( p2 U( B! U
}( @( R1 C+ }  m. _) v# M  o
//insert插入操作处理
* ^0 t8 b' L7 f& ]bool mysql_insert(THD *thd,TABLE_LIST *table_list,List<Item> &fields, List<List_item> &values_list,* s, y9 ?5 P" c& B0 E5 @! h% p
                  List<Item> &update_fields, List<Item> &update_values, $ l0 t: ?" S5 K! h
                  enum_duplicates duplic, bool ignore)
8 n. Y9 s& H! c# G" ~4 h{
3 o7 G# k9 b" t3 P/ O      while ((values= its++))
( H5 L& N: e9 x3 h: F2 Q      {' n1 D  }9 \7 H; G$ p' B: s
           error= write_record(thd, table, &info, &update);
7 v& h3 n0 F' f+ D      }
# ?9 h. k$ y7 z) S3 j+ I}- [' o( t& o* L
//写入记录
! A  N* S6 v2 J( z' A( rint write_record(THD *thd, TABLE *table, COPY_INFO *info, COPY_INFO *update)
7 x1 k2 V1 V' _/ A0 f2 D% f{
2 U+ P: R# o7 _0 E* U: g5 y* m, M4 c    if (duplicate_handling == DUP_REPLACE || duplicate_handling == DUP_UPDATE)
5 [8 ^$ b& H0 f: N    {
" i0 T- p3 T9 Q8 X) {0 {, |         // ha_write_row  重点是这个函数
( N! c# b, f. s# q+ A. u         while ((error=table->file->ha_write_row(table->record[0]))), h$ c7 @3 P8 l2 J. K$ r
         {1 E* t& y0 _% Q% ^8 p0 ^) Y0 r
             ....
8 o1 d( ]7 z. K0 r/ M/ b         }3 ?  u# T/ e% A
    }$ l1 |1 L+ c3 u7 }" m
}4 N( t8 B& A" X5 G

7 \) m6 D8 ^( e  K0 |
& j' n- w' x, `: f1 j9 x7 h! G  q! P) F/ g2 a3 {
可以看到,调用链还是挺深的,追到 ha_write_row 方法基本上算是追到头了,再往下的话就是 MySql Server 给 Storage Engine提供的接口实现了,不信的话继续看呗。。。* l' w- W, i0 o

  x  _1 r3 B! J( s  l1 C( q5 f$ z<3> 继续挖 ha_write_row+ p. f* M3 V+ l( a* i
7 \9 C' T. t6 s! p6 g6 E
int handler::ha_write_row(uchar *buf)& w: Y# O' [6 M0 a
{1 l5 f% |( S( E6 U! J
    MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_WRITE_ROW, MAX_KEY, 0,{ error= write_row(buf); })& F& u& O" _6 n8 ]7 W. ?; l
}9 s2 Q! e0 L; J2 Z  s$ z+ n6 l

0 j5 s( ~6 f7 K+ P0 h//这是一个虚方法! ^! U, L. _0 w0 Q' S" @
virtual int write_row(uchar *buf __attribute__((unused)))
5 S& }- p& |  |6 _% _. i: t, }# N{! F3 f3 H4 |0 T5 }
    return HA_ERR_WRONG_COMMAND;
- i1 u- s' C7 Q# P' D% K: E  h1 O}# _1 g% M5 L, G
1 Z6 H" t6 b8 P8 V

) ^; ]% E. g" Z& \+ J看到没有,write_row是个虚方法,也就是给底层方法实现的,在这里就是给各大Storage Engines的哈。&#128513;&#128513;&#128513;
( S  h/ S" E( E: |. M$ Z4 K1 R
% r- S4 A  M6 |" b9 N3. 调用链图
( a7 I3 ?6 Y7 U0 z5 K4 U$ _; r这么多方法,看起来有点懵懵的吧,我来画一张图,帮助大家理解下这个调用堆栈。
0 y  T. w4 w/ Z
$ D, r% m6 u, P/ K& ~9 h* U6 D5 k- ?. |/ d

# ~- H8 A, t. i% H0 b% j$ [: U  T9 b三:总结- `! C# P; T. z: `9 G" _0 @
大家一定要熟读架构图,有了架构图从源码中找信息就方便多了,总之学习mysql成就感还是满满的。+ D3 N. }7 c& `) k( N; P3 m
————————————————
* |2 [1 J4 X! g版权声明:本文为CSDN博主「一线码农」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
, {9 p, O; E7 k+ X4 ^$ O原文链接:https://blog.csdn.net/huangxinchen520/article/details/106487415+ U: Q1 c: S7 l: l





欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) Powered by Discuz! X2.5