数学建模社区-数学中国

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

作者: zhangtt123    时间: 2020-6-3 10:36
标题: MySql轻松入门系列————第一站 从源码角度轻松认识mysql整体框架图
一:背景7 ^9 u+ C9 k; U
1. 讲故事
0 E4 m# G  e4 E最近看各大技术社区,不管是知乎,掘金,博客园,csdn基本上看不到有小伙伴分享sqlserver类的文章,看样子这些年sqlserver没落了,已经后继无人了,再写sqlserver是不可能再写了,这辈子都不会写了,只能靠技术输出mysql维持生活这样子。
- q7 @7 G$ _( q7 Y5 ?' f! X
* a; K  V2 Q& `3 J; X6 s. T, w9 R二:了解架构图
/ X; _# K, U% X1 p3 D5 Dmysql最大的好处就是开源, 手握百万源码,有什么问题搞不定呢? 这一点要比sqlserver爽多了,不用再dbcc捣来捣去。/ L6 |, ^) L8 ~4 @7 C$ p
) \9 S$ o# b8 {$ w0 \( |" t
1. 从架构图入手- m6 H/ D( F- d, c
大家都知道做/装修房子都要有一张图纸,其实软件也是一样,只要有了这么一张图纸,大方向就定下来了,再深入到细节也不会乱了方向,然后给大家看一下我自己画的架构图,画的不对请轻拍。
- F* B6 \# `, Y% A4 h. K/ G) @. t- V# t- o
- t! o& e4 w5 r* e

2 K+ c' C; j9 \7 \其实SqlServer,Oracle,MySql架构都大同小异,MySql的鲜明特点就是存储引擎做成了插拔式,这就牛逼了,现行最常用的是InnoDB,这就让我有了一个想法,有一套业务准备用 InMemory 模式跑一下,厉害了~/ |: O9 _) r# k7 b- L4 Q; ~3 @5 ^% b% n
* L9 T- C' ?" i, J
2. 功能点介绍1 V0 \. w% w. Y  m. g& U
MySql其实就两大块,一块是MySql Server层,一块就是Storage Engines层。
/ H& M- Q/ f; A* q8 u
* F6 F' m' i+ X<1> Client
3 [) B1 v  Z" d( ^8 a1 v1 v不同语言的sdk遵守mysql协议就可以与mysqld进行互通。
" a2 f. }: ^0 \2 l9 P2 _2 e8 t( t! o9 l; I8 T" k0 L
<2> Connection/Thread Pool
/ I. h3 ^1 E( q9 S+ c% z) UMySql使用C++编写,Connection是非常宝贵的,在初始化的时候维护一个池。
( l) m* ^! \0 e3 k( h: I/ G0 U& P) a3 {/ L: ]# p) B6 ~
<3> SqlInterface,Parse,Optimizer,Cache3 ?6 J+ O. |% `! q1 T
对sql处理,解析,优化,缓存等处理和过滤模块,了解了解即可。
0 l& R5 a% L: k  C! ?& ~" Z& q! n- n% I% M
<4> Storage Engines
& E6 f# o  n7 I- h4 P2 t, B$ B* U负责存储的模块,官方,第三方,甚至是你自己都可以自定义实现这个数据存储,这就把生态做起来了,&#128046;&#128067;。
* k' v) s- }+ h9 F0 j" Y% X4 o  a, d. Q" @; l" W: ^, o4 r
三: 源码分析" U( d% ^& x' R$ q; G9 f
关于怎么去下载mysql源码,这里就不说了,大家自己去官网捣鼓捣鼓哈,本系列使用经典的 mysql 5.7.14版本。
  a8 j% c) n8 A# f( y# ?& N
$ k* [5 Q3 k' Q$ _1. 了解mysql是如何启动监听的
, `' k/ P; Z8 z. T手握百万行源码,怎么找入口函数呢??? &#128513;&#128513;&#128513;,其实很简单,在mysqld进程上生成一个dump文件,然后看它的托管堆不就好啦。。。
" S' T! G" V9 ]4 m2 ^+ c1 g/ ?  j/ d3 N% R9 u

1 R* H* a0 j2 i+ H
+ _; q  ~8 X1 {$ V$ }* X& t从图中可以看到,入口函数就是 mysqld!mysqld_main+0x227 中的 mysqld_main, 接下来就可以在源码中全文检索下。- V2 b- I7 H( `5 D; c
  [. c( M. r/ M5 ^7 _( n; B$ _6 a
<1> mysqld_main 入口函数 => sql/main.cc( h  E2 g+ t7 E5 a
% z: K+ K7 C. H' {$ L/ r0 @+ {& v6 k
, W1 a+ }# r; P4 N2 [  R
extern int mysqld_main(int argc, char **argv);
% A: ^9 o2 G3 b5 l) c( O9 B0 a" P1 D  ~; t0 u' u( H
int main(int argc, char **argv)) u- w  R/ t* E8 z* J  Q
{
- q( ~) K+ Q% H  return mysqld_main(argc, argv);) w! R+ I' l; e
}# B  W6 o0 `: Y" ^; t/ F. v: ^
2 F( g& }" o' n6 E, g* }! C8 w

1 ?. t0 D7 X) P2 E) v5 Z这里大家可以用visualstudio打开C++源码,使用查看定义功能,非常好用。
$ l0 x$ @. l1 a
4 X0 \2 @: l( ~- e7 O) b- @<2> 创建监听2 \; l, `; ~! x) k6 Y) J/ V
# W, s1 p& E0 W; q' A' A
+ v9 |, J& f" G6 h
int mysqld_main(int argc, char **argv)* c; M2 c5 d1 O1 ]8 a$ e
{
& o0 A, L2 ^3 i. A9 |4 u    //创建服务监听线程# C0 G: E, v! r
    handle_connections_sockets();
/ J9 d) P6 P7 A6 A: @2 E}
( e0 ]& J: m; b8 A% P9 M* D
# j( x) ]' a* G1 m7 vvoid handle_connections_sockets()" v) f8 y$ j  H- ]; z
{+ R3 z3 M6 z/ U7 t
     //监听连接1 g1 u5 \) k0 W4 J& E3 u+ u4 H
     new_sock= mysql_socket_accept(key_socket_client_connection, sock,
0 F, G, E- l& X% a& Y                                    (struct sockaddr *)(&cAddr), &length);% N+ j) Q  y, l0 w
, ^, M( \2 L) n+ E; h
    if (mysql_socket_getfd(sock) == mysql_socket_getfd(unix_sock))
0 U: Z5 j" o4 a0 }# w      thd->security_ctx->set_host((char*) my_localhost);9 @$ r0 m0 Z; ]" f; `) J

. ]& |3 Y$ U7 A    //创建连接1 z, [0 ^! _# t9 s% [+ k5 c
    create_new_thread(thd);
' [, }* ~: N6 k6 q' c" B}, Z/ n1 O  ]. ?
" F3 \- [- k4 j( @6 s9 O
//创建新线程处理处理用户连接
" V0 J, O3 z$ p- [static void create_new_thread(THD *thd){, q2 V% Z2 g9 U( d  `# m/ K
; q- Z2 z$ ^, C% X4 y) c# Z: A! P
   thd->thread_id= thd->variables.pseudo_thread_id= thread_id++;
. R6 c2 _& U1 ]: G+ n2 V( D+ R% u
   //线程进了线程调度器$ F) y; j6 W1 q2 w6 x; i
   MYSQL_CALLBACK(thread_scheduler, add_connection, (thd));   , b1 o* I$ F7 @+ V: S* U
}
2 a1 f- z& ^5 A# h# A
( j8 x. R- I4 X4 ?
4 @  j  w+ u1 ]至此mysql就开启了一个线程对 3306 端口进行监控,等待客户端请求触发 add_connection 回调。
* @) a* N4 {# w) l' ~0 ]! [8 H! w0 ~5 u
7 `4 f! O5 ~, z, q! ]# K
2. 理解mysql是如何处理sql请求( @3 B8 z6 F7 l9 p
这里我以Insert操作为例稍微解剖下处理流程:/ b% O/ h) i; W3 T. Z3 u2 W
6 @' K- a% {  |# ]# o6 S% `5 n
当用户有请求sql过来之后,就会触发 thread_scheduler的回调函数add_connection。
- ~, c: S1 K# \( D  e3 A
' F/ m! B, h6 f* Z& I+ k# x: a# I( v& k
static scheduler_functions one_thread_per_connection_scheduler_functions=% C1 J5 M& p# I- o& ?3 r9 D" ]
{; r  y  g6 r# r) `
  0,                                     // max_threads
6 T4 Y( L! F5 v. o/ S' y  NULL,                                  // init
, E# k% _7 h2 Y3 r! z1 b  init_new_connection_handler_thread,    // init_new_connection_thread
. v, u" B, p+ ?) L0 k  create_thread_to_handle_connection,    // add_connection* t$ `9 k' n, e; `+ @) S1 U
  NULL,                                  // thd_wait_begin
% p8 l6 E" w- I! u7 W8 E: V" a  NULL,                                  // thd_wait_end
: `& Y$ q9 O  a1 \* W) N5 g9 c  NULL,                                  // post_kill_notification3 D. u6 f' x  R5 q
  one_thread_per_connection_end,         // end_thread
0 K  k7 _( o3 Q4 S. ~$ f2 ~( \  NULL,                                  // end" U3 I) B# K2 N
};! n* g/ y( d. a0 l- Y

; _8 j  ~( V* r  `% Y* }% c
0 e, i; o" G2 O$ F% M# k% a从 scheduler_functions 中可以看到,add_connection 对应了 create_thread_to_handle_connection,也就是请求来了会触发这个函数,从名字也可以看出,用一个线程处理一个用户连接。3 E' ^' I* F8 C" F' [  X6 `+ k
6 i  R: S# l# i! k8 `8 G0 t( `
<1> 客户端请求被 create_thread_to_handle_connection 接管及调用栈追踪) [7 X& ^% z) l
/ D5 u! O, K0 `6 N& T# S
void create_thread_to_handle_connection(THD *thd)" g" K4 t! i$ X+ B7 M) ?9 r
{0 B( \" `9 \$ [  O% ~7 ]- L, R
     if ((error= mysql_thread_create(key_thread_one_connection, &thd->real_id, &connection_attrib,
) d3 {! U. q3 A. D( m                                     handle_one_connection,(void*) thd))){}( `( K1 A8 @( T& E
}
( h4 u6 R5 C) W3 W& B//触发回调函数  handle_one_connection
' w* _1 T2 B) s; p* Spthread_handler_t handle_one_connection(void *arg)
" D# x( _( l4 X4 `  Z. F8 ]{2 w: r2 h, v2 `& f
     do_handle_one_connection(thd);7 N1 _7 L' e  S$ b- I
}8 K& Z, D& i0 n1 B
//继续处理* d8 Q6 {$ Y  T/ g: K2 i
void do_handle_one_connection(THD *thd_arg){7 |% Z4 r  F! S" h- ]& n
    while (thd_is_connection_alive(thd)); h! @" b8 c9 t( m3 a( C
    {" g4 X% ], }. P3 {' h% q) `" D5 `% l. M
      mysql_audit_release(thd);
' E/ c- Q0 g: i      if (do_command(thd))  break;  //这里的 do_command 继续处理; A) _6 J  w5 S5 Q. x6 q
    }
2 w8 j9 r! h5 _6 {}2 H7 c! X! s! R( x0 V# J& D
//继续分发
9 \0 }5 ]9 Y7 z5 x! vbool do_command(THD *thd)0 ~, v) I5 V  I- H  @! O! p# a2 ^
{
; A2 `; h4 ^  r& i4 E    return_value= dispatch_command(command, thd, packet+1, (uint) (packet_length-1));
0 P, `7 b6 Z1 g; B9 [}4 O6 U! U6 t. k# r* e
bool dispatch_command(enum enum_server_command command, THD *thd, char* packet, uint packet_length)1 t9 k/ ~: j% O/ X# ?
{
# G- M2 e: j( v& \# u0 E, Z      switch (command) {
6 r$ t6 P. k& d2 h         case COM_INIT_DB: ....  break;8 N* m* W7 G0 H6 |2 V( f* |
         ...
1 ?8 J' M, ]( V) [         case COM_QUERY:   //查询语句:  insert xxxx
; {* e4 F) u3 I# H" d  k* w: N             mysql_parse(thd, thd->query(), thd->query_length(), &parser_state);  //sql解析
! {5 o, p+ T% x+ \2 m5 ?& d5 z           break;
: \  I& v5 a$ E* W) r( N6 l1 L      }
" g1 D: W; _: r  D" w  v}
) |: X+ l; y$ I/ i( M& o# R//sql解析模块9 L! B" u; L- j7 D. I6 F( d* R
void mysql_parse(THD *thd, char *rawbuf, uint length, Parser_state *parser_state)- T" I1 e6 I& Y
{
+ h3 s1 W+ W  F      error= mysql_execute_command(thd);
2 ?# t( ]9 Y! j  w( C; ^}2 b" Z* T6 v3 ?1 ]6 x* _' N1 ~. X
& s; M6 z! c" U5 }3 U  e8 w7 c
( d8 ?; v% J  F' x" X' Q
<2> 到这里它的Parse,Optimizer,Cache都追完了,接下来看sql的CURD类型,继续追。。。
* c* g; N  c3 C8 M, U+ U% s/ ]7 m/ Y5 J3 q( b
//继续执行
4 }3 q! Q. G6 ^( h& Bint mysql_execute_command(THD *thd)& ~; w6 Y# }% B, D: \& [: n
{$ V3 A+ e% Z$ u0 |& g. J* P, _' K% k# l2 ]
  switch (lex->sql_command)
( _& Y& u2 Q% x7 J+ J6 H& U9 G) d2 {  {) M4 _0 R+ }2 _& g, r# `
      case SQLCOM_SELECT:  res= execute_sqlcom_select(thd, all_tables);  break;% T+ N9 u3 F2 ]: A8 u5 Q
% R( s" ^! u8 S  `* z+ J
      //这个 insert 就是我要追的
8 V8 W0 |  h  ^2 G+ K0 r      case SQLCOM_INSERT:   res= mysql_insert(thd, all_tables, lex->field_list, lex->many_values,
+ [2 d! E: ]' w- F7 p7 |                                              lex->update_list, lex->value_list,
" [) s/ z( s; ^8 I                                              lex->duplicates, lex->ignore);' A7 G3 }; X& t$ h0 L
  }- s5 [6 m- I$ y
}
" b" ^6 H# l# m" u; t! S3 B5 x* }//insert插入操作处理
6 i6 u8 u& g7 A$ M5 n- `+ ^3 X$ r1 nbool mysql_insert(THD *thd,TABLE_LIST *table_list,List<Item> &fields, List<List_item> &values_list,
- I5 |" {: p3 |. m5 P; `9 q                  List<Item> &update_fields, List<Item> &update_values, : w) T& B' f) Y2 p* h) i/ w
                  enum_duplicates duplic, bool ignore)( h' i4 u) C1 X; }+ \* `
{
& b" p. [  {0 j0 T6 q      while ((values= its++))- f, |! Z* }3 `, k6 H/ f* R8 k
      {# ~* g: ?# Z0 `9 ~8 L. J' n
           error= write_record(thd, table, &info, &update);
; _) J, N4 C, u; u; k      }
$ r6 G9 F8 E& ?; ]. a}' P: K" N  K6 q- ^" {, Y- L9 V
//写入记录0 e9 ^- @; f" e( `
int write_record(THD *thd, TABLE *table, COPY_INFO *info, COPY_INFO *update)
) Q# [" ?$ `# `7 n  k# ~{- o3 G4 f$ M" K) M+ l- k; s
    if (duplicate_handling == DUP_REPLACE || duplicate_handling == DUP_UPDATE)0 c% O6 S: l+ V+ W
    {' h, ~8 ]6 ?, V* j+ r+ {4 C$ u
         // ha_write_row  重点是这个函数- f0 K% G+ x) P7 B
         while ((error=table->file->ha_write_row(table->record[0])))
4 l6 r+ q; j( T1 ~9 O+ T0 j* l         {" t3 X7 P( ?/ h% R6 z
             ....
% [9 T6 t$ {' M5 @% t/ v; o3 X         }3 n: W7 a1 E/ X: {/ G, t; W, A
    }
4 D" }6 X0 x" Q/ B# ]4 h. l* `4 a}" P) V$ L; Q$ x9 F& E1 t
, u6 X. H" k) B; ?: o% ^

$ ]2 l% t' S1 }3 L: Y6 Q
% O* d1 q7 e7 C# s9 [可以看到,调用链还是挺深的,追到 ha_write_row 方法基本上算是追到头了,再往下的话就是 MySql Server 给 Storage Engine提供的接口实现了,不信的话继续看呗。。。& \% _1 [1 o  [& m

+ }. l3 _) L- Q  c! c4 C0 _<3> 继续挖 ha_write_row
9 M7 P" a4 r6 {# t
; o( X! g7 L" N$ Gint handler::ha_write_row(uchar *buf)
/ v+ D  w1 q9 S! L, ~$ ^- W  y{
4 ]+ q, v! z5 ~8 q. @4 ~' ?( Z    MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_WRITE_ROW, MAX_KEY, 0,{ error= write_row(buf); })
3 N6 P$ j. r7 E' ^}
, \& ]- u5 K) F/ l0 Z% M9 G) Q+ S& M. o
" ~8 A: }6 P- l! U: f4 a//这是一个虚方法1 g5 p$ O6 t9 j
virtual int write_row(uchar *buf __attribute__((unused)))% J6 J  ~' `4 z9 F: v% P  Y* s4 v  m
{
+ E, x7 l  d7 s" X5 n    return HA_ERR_WRONG_COMMAND;
0 F8 r& s( G3 k# o7 P4 S}+ s+ x1 u8 j8 e
  [- r7 m3 P; E$ \

% `+ J/ @' [) D% `( O1 N3 I看到没有,write_row是个虚方法,也就是给底层方法实现的,在这里就是给各大Storage Engines的哈。&#128513;&#128513;&#128513;! ^$ n- E, J) `2 _8 e+ u, N6 o
$ Q: U; M* B7 L5 K: R; E/ c+ P/ }
3. 调用链图
2 z0 P! K+ K' f* |$ ^; ?, I/ J这么多方法,看起来有点懵懵的吧,我来画一张图,帮助大家理解下这个调用堆栈。
7 O, U0 Y( Y+ j0 I+ {- c6 b) G9 A
8 ~4 U. n6 k  b  _* {1 a; V8 V3 r
) G3 c7 Z2 Z) }& S, j# M  d( i3 w7 D* A9 U7 h
三:总结$ D* _& d& k% @$ I9 [" O
大家一定要熟读架构图,有了架构图从源码中找信息就方便多了,总之学习mysql成就感还是满满的。
+ h, B& |3 Q+ |2 ?3 P————————————————
' K$ ~# w3 Q5 T9 m0 F; [版权声明:本文为CSDN博主「一线码农」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
9 n* g: d/ n% Y/ |( f原文链接:https://blog.csdn.net/huangxinchen520/article/details/106487415
. p+ n" o/ E0 z$ J




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