数学建模社区-数学中国

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

作者: zhangtt123    时间: 2020-6-3 10:36
标题: MySql轻松入门系列————第一站 从源码角度轻松认识mysql整体框架图
一:背景1 u; ?6 v4 Z" x# V; k
1. 讲故事
$ w8 g' Q" N5 k最近看各大技术社区,不管是知乎,掘金,博客园,csdn基本上看不到有小伙伴分享sqlserver类的文章,看样子这些年sqlserver没落了,已经后继无人了,再写sqlserver是不可能再写了,这辈子都不会写了,只能靠技术输出mysql维持生活这样子。
. s* ?# G: v# c  C3 X3 F0 h: y; ]  ~1 P& \  w
二:了解架构图! o' N5 C  o! M3 l7 ~0 [  \. B
mysql最大的好处就是开源, 手握百万源码,有什么问题搞不定呢? 这一点要比sqlserver爽多了,不用再dbcc捣来捣去。
+ s: `& V$ R! l; ]) T& P  q# R0 z/ ?( D7 G
1. 从架构图入手
4 {" {6 u! o, R; g8 c7 j4 g大家都知道做/装修房子都要有一张图纸,其实软件也是一样,只要有了这么一张图纸,大方向就定下来了,再深入到细节也不会乱了方向,然后给大家看一下我自己画的架构图,画的不对请轻拍。
2 b8 C) E4 _7 G8 V1 k% T9 H$ {5 m( F1 S
- `( u* `2 Q% l% f" ~0 T

, J7 r  W2 T- W% ?3 _' O/ D其实SqlServer,Oracle,MySql架构都大同小异,MySql的鲜明特点就是存储引擎做成了插拔式,这就牛逼了,现行最常用的是InnoDB,这就让我有了一个想法,有一套业务准备用 InMemory 模式跑一下,厉害了~
! l( L" @2 g; `* Q0 {
/ a  U4 e9 _4 Q2. 功能点介绍
7 m  O- v  |3 _7 c  v2 R/ GMySql其实就两大块,一块是MySql Server层,一块就是Storage Engines层。1 u1 ]0 S6 b8 ~+ G5 I8 n5 g

6 R  y: S8 s: m<1> Client
( h3 s( I; J- M不同语言的sdk遵守mysql协议就可以与mysqld进行互通。2 n3 M& Y. N1 W' T5 j
: y/ u! s( p% K+ ?0 e; H
<2> Connection/Thread Pool
* F: `' r1 Z8 L" n( ?MySql使用C++编写,Connection是非常宝贵的,在初始化的时候维护一个池。6 f0 a0 X$ @1 _- ^9 d) h

7 ?7 f4 S4 U5 v- S3 A% s  \<3> SqlInterface,Parse,Optimizer,Cache  V+ D6 _; [. v! K8 B* j; g
对sql处理,解析,优化,缓存等处理和过滤模块,了解了解即可。' [7 P+ p0 h2 \. z- g6 @

* U+ E- w) ^! ^! w7 y<4> Storage Engines
9 s/ K( b- f1 T  v* q- W负责存储的模块,官方,第三方,甚至是你自己都可以自定义实现这个数据存储,这就把生态做起来了,&#128046;&#128067;。2 T' n# o  T: J! y, H) s

/ w% {9 h0 D. L( n& |7 V三: 源码分析& W& U, h  h0 K0 Q8 ~3 E" F" j& `
关于怎么去下载mysql源码,这里就不说了,大家自己去官网捣鼓捣鼓哈,本系列使用经典的 mysql 5.7.14版本。5 q0 e$ c$ Y4 Q/ x
  g/ }0 @, N$ ]6 K  |# h5 l
1. 了解mysql是如何启动监听的, W3 r. ]$ V& M- t: A% J
手握百万行源码,怎么找入口函数呢??? &#128513;&#128513;&#128513;,其实很简单,在mysqld进程上生成一个dump文件,然后看它的托管堆不就好啦。。。
4 ]0 o7 v/ l6 x9 X% P, m5 q, N7 A
8 y3 b2 x$ G% D9 _5 I0 k1 T" `& M- L' L; w1 q
9 k* \% I, ]4 A$ F" H
从图中可以看到,入口函数就是 mysqld!mysqld_main+0x227 中的 mysqld_main, 接下来就可以在源码中全文检索下。2 T/ m/ y$ }4 q7 a* T

! L% X* L0 W6 v; h2 N<1> mysqld_main 入口函数 => sql/main.cc
- J: _7 f# n8 d4 k; L) h5 @& @# v" A* j8 Y2 k

4 K" ]3 g* Y! V# o# H! Q1 @extern int mysqld_main(int argc, char **argv);9 I2 K: J0 i* z8 c7 m

& w( ~: n; x; D3 P6 Eint main(int argc, char **argv): Y; x+ A: [  V  J4 }. S: _
{6 j+ O, ~# g/ a6 P/ Q
  return mysqld_main(argc, argv);* i- @% w1 P/ i; D+ Z+ F% ^
}
  q3 a  c2 s" a
: r7 |1 q3 Z) W5 x9 Y, g1 m
, Y' E& q6 g. n8 T这里大家可以用visualstudio打开C++源码,使用查看定义功能,非常好用。# F% z6 S3 N4 l& u1 r$ f
1 q5 y2 p: {: E) M& V+ e) v
<2> 创建监听
2 q0 O& f# f$ _3 H" m/ M
" `% T. K. w8 n* [, j* c2 ?4 l! I! I- E& q
int mysqld_main(int argc, char **argv)
+ Q1 q+ C) F: K  g$ Y3 l  ?{- Q1 G1 d" `6 u
    //创建服务监听线程% K, a& y8 h$ \9 S
    handle_connections_sockets();+ C. ]' O7 f% m' g3 Z; M* X0 T
}8 z: Z) R1 n3 S1 r0 V+ m2 Q

+ z% H. r$ P' d7 O* lvoid handle_connections_sockets()" j. s. p; v! H8 y$ v) C9 A8 p
{
( T3 V" H6 }' H5 R# z     //监听连接% f1 n. @' l6 n3 H" r, b( {4 G- `
     new_sock= mysql_socket_accept(key_socket_client_connection, sock," C' o4 f0 R3 Z; r( X- e8 \! F
                                    (struct sockaddr *)(&cAddr), &length);
7 s; @& V. \0 |
# b6 L% D/ I. c    if (mysql_socket_getfd(sock) == mysql_socket_getfd(unix_sock))3 u7 }# E2 t2 j! l3 ]( H2 f$ f
      thd->security_ctx->set_host((char*) my_localhost);/ `  _1 U8 |4 }
+ @; c  Y. M. b
    //创建连接( F1 H. ^# R- t" r4 D9 u0 v" S
    create_new_thread(thd);
% g) E7 E; o: f}& _3 f, P( [6 U8 g3 _* @  Y# }* a
& w. W4 P5 ], d$ l' S
//创建新线程处理处理用户连接& t$ m0 u- M( A( |0 V
static void create_new_thread(THD *thd){
" Z6 g( l- e5 o+ U
8 H& F% k: @5 T   thd->thread_id= thd->variables.pseudo_thread_id= thread_id++;9 h5 U: R: {/ z. Z: I) E0 e
% k: c) l8 y: _9 Y6 Y/ w
   //线程进了线程调度器
$ D- L% M4 u2 F4 Z$ h% {! x   MYSQL_CALLBACK(thread_scheduler, add_connection, (thd));   
+ b1 @2 X1 ^+ w2 Q5 P}6 P2 L3 h) y7 U1 C
; k7 n/ N9 J1 U& u  t8 l

0 L( w0 E' P! n5 I3 O5 o/ M5 X至此mysql就开启了一个线程对 3306 端口进行监控,等待客户端请求触发 add_connection 回调。6 x% N9 C4 f+ w+ H6 u5 v. C
& e- |* ?( M7 k- t: h0 ?7 l  V  r

# d! Y, h1 x* k4 j2 x  s% y- O. U2. 理解mysql是如何处理sql请求4 _$ ]# u6 }- G" s5 \
这里我以Insert操作为例稍微解剖下处理流程:3 R6 M# d+ U3 x8 _
- p$ O$ e" k. T8 q$ p  T3 i
当用户有请求sql过来之后,就会触发 thread_scheduler的回调函数add_connection。! _% m: v; H& x6 Z7 V" D9 Y
6 l* o& D5 F6 e8 C
# _4 }* A( D# g' @: p
static scheduler_functions one_thread_per_connection_scheduler_functions=' B2 Q8 r2 c+ Q$ Z& i2 d! q; s- o
{
- i  d+ r1 ^$ a' U2 X2 ?  0,                                     // max_threads
2 F4 N; k+ a$ ]8 k$ \  NULL,                                  // init
$ E. \' |( }" ?7 _: x6 M5 P  init_new_connection_handler_thread,    // init_new_connection_thread
" J6 C+ t5 E: a" }  create_thread_to_handle_connection,    // add_connection
! _4 P+ b7 k( G4 ^3 g  Z  NULL,                                  // thd_wait_begin
; o. c( k9 W8 U. ]0 f  NULL,                                  // thd_wait_end$ d! l: |: Q6 i! i  r
  NULL,                                  // post_kill_notification
- Z& j) n% _  l8 p6 o, S  one_thread_per_connection_end,         // end_thread
$ {5 s3 V  h# N5 \, t  NULL,                                  // end
& N4 A0 S/ O' X1 X0 v};  w& I- L  w/ d% I3 _: ^, `
: ^$ t$ _0 P2 ]% E' K9 }  R- E
8 r1 q6 p5 s: @! a4 i' ~- s# L
从 scheduler_functions 中可以看到,add_connection 对应了 create_thread_to_handle_connection,也就是请求来了会触发这个函数,从名字也可以看出,用一个线程处理一个用户连接。
8 f4 j: J" t, ]7 o
6 |* }4 K1 W, c$ a  C% r0 h0 P<1> 客户端请求被 create_thread_to_handle_connection 接管及调用栈追踪, i0 ~0 {) B2 o
8 H; S3 \/ ?2 U! |  t% n' W* r
void create_thread_to_handle_connection(THD *thd)
: b7 P; z4 j, a3 p{
) [" g) \0 P, v* l& u     if ((error= mysql_thread_create(key_thread_one_connection, &thd->real_id, &connection_attrib,9 c, X% O$ D4 g. I- q- i- }
                                     handle_one_connection,(void*) thd))){}! g  Z7 E- l' J- Z: r( C! \
}
8 h4 r% f1 |& g% p& [//触发回调函数  handle_one_connection2 w0 V  |6 Z) j& q
pthread_handler_t handle_one_connection(void *arg)% Q* O) ~9 f4 k8 K  T
{2 E$ y8 `. ]( W/ l& Y, Y1 u
     do_handle_one_connection(thd);
* B+ }% S) ?. A/ s. }* S" A) W}
# t: X$ ]- \$ [- a5 {5 \7 R4 x//继续处理0 v$ |# X6 O* J
void do_handle_one_connection(THD *thd_arg){% g0 |- t, `" J
    while (thd_is_connection_alive(thd)), ]5 H& d0 E0 d" A
    {
5 h$ `# Z2 C. E; z) A, L: v      mysql_audit_release(thd);3 T4 ~! T! z  u2 p$ @7 [
      if (do_command(thd))  break;  //这里的 do_command 继续处理
) `1 y' n7 q) H7 o    }
  o. `/ O+ q& [. Q8 Z' E}) E) }( z/ W) @. T8 @
//继续分发/ L/ ]( j. {% E$ P1 t3 w
bool do_command(THD *thd)
* @) s% q5 t1 X) s{" E% s  Y' \) ]* h) x( X
    return_value= dispatch_command(command, thd, packet+1, (uint) (packet_length-1));9 k# s5 C8 [: |: T' @/ h
}
4 ~+ ?/ d3 }& ]4 |* Obool dispatch_command(enum enum_server_command command, THD *thd, char* packet, uint packet_length)
4 G/ A" |3 X, P% R( A5 k{8 h4 l5 {% `+ j) r
      switch (command) {; X, G( k/ W$ X. y& N
         case COM_INIT_DB: ....  break;
& R* u/ o: X8 f& C         ...$ x8 p9 ]' J- q/ _% Q$ ]% g
         case COM_QUERY:   //查询语句:  insert xxxx3 a  r1 R3 K- G: N' i
             mysql_parse(thd, thd->query(), thd->query_length(), &parser_state);  //sql解析# Q4 X6 d1 e4 }8 p
           break;
/ G6 t% r+ i9 X$ ~( S* J      }1 _; N" z8 {0 |
}- a0 @5 i7 I: D
//sql解析模块% \. |7 ~0 g& w* i6 ?2 L
void mysql_parse(THD *thd, char *rawbuf, uint length, Parser_state *parser_state)
& T, A9 l+ J4 o0 I* C+ K{
, A5 c* M7 _# k( s      error= mysql_execute_command(thd);" Q( Z9 c* W/ X6 d- t: I
}- Q) f* U- _4 E7 P8 ?" n
0 f9 k( ^& p2 M3 G# o

' m! a: o, `3 {$ o, B! |<2> 到这里它的Parse,Optimizer,Cache都追完了,接下来看sql的CURD类型,继续追。。。# N. B' w0 ~) ?1 s

' i; [! _( ~9 D1 _6 C//继续执行
" O' `5 T1 |/ A# j4 t* J% Lint mysql_execute_command(THD *thd)% R% D3 m  t3 F: W, _, b
{
; e& {  _& i, `8 B8 e8 W8 d/ Y  switch (lex->sql_command)
1 [8 y" u$ a! E2 v( _' o5 x  {5 S4 P5 ?3 l# C6 L/ d8 |! q1 H
      case SQLCOM_SELECT:  res= execute_sqlcom_select(thd, all_tables);  break;7 R6 r8 E8 Z  u. N) n; H

6 b. p5 R$ q( ]: W& |5 X' E! ?      //这个 insert 就是我要追的
9 K# p3 W# Q6 G* K! w      case SQLCOM_INSERT:   res= mysql_insert(thd, all_tables, lex->field_list, lex->many_values,
3 @8 |) }5 j0 n7 l6 ~2 @! \                                              lex->update_list, lex->value_list,* x! C* ^' R% G8 `/ B  W
                                              lex->duplicates, lex->ignore);
6 F- h' p: j# g  O8 D  }
& C$ a! y/ [6 }, f}
% p5 @# _7 I% [$ B# ^//insert插入操作处理
2 s$ |" d" S; c. Q% F7 qbool mysql_insert(THD *thd,TABLE_LIST *table_list,List<Item> &fields, List<List_item> &values_list,
, Q6 ]. U: r/ q6 T6 U' I9 ?                  List<Item> &update_fields, List<Item> &update_values, ; @4 R* q3 a) g. m
                  enum_duplicates duplic, bool ignore)
/ g; x' e& g" [4 e{
; w7 s& n- l6 ]      while ((values= its++))
) \5 I6 S" d7 O* R7 I. U      {
" k. y7 _. w) X) Y" B3 ~           error= write_record(thd, table, &info, &update);' \7 b6 W  |# V
      }( t# I4 h# G+ _( m/ f5 T
}
- e2 ]  E0 H" S1 \7 z" R//写入记录$ w( }) G( }/ S% T
int write_record(THD *thd, TABLE *table, COPY_INFO *info, COPY_INFO *update)
& j8 M; E6 M6 Z4 F/ c- t/ m{3 H$ C2 D0 d& b6 E& z
    if (duplicate_handling == DUP_REPLACE || duplicate_handling == DUP_UPDATE)" C- k/ y( Q9 h' G5 w) K$ k
    {0 @5 a; W2 R" T
         // ha_write_row  重点是这个函数7 R& q3 v5 ^% @: [3 _& b8 S+ g( F& z
         while ((error=table->file->ha_write_row(table->record[0])))
% W8 \) z* Z! i1 |/ `         {. Y: e  B5 E& M3 k; X) o+ k
             ....
; v4 ?$ x& A1 m8 n; p- |         }
; G" U0 \6 z5 [* P$ }& T' z/ C9 q( ~    }
  y& k+ Y8 K2 y3 c% c5 Q3 I}& m9 c% T, u. ?& k2 H. `& o
' J: e: K9 x; e: Z

& ?) S6 `. D" A" Y2 @
) a: T2 N8 x, v: Z可以看到,调用链还是挺深的,追到 ha_write_row 方法基本上算是追到头了,再往下的话就是 MySql Server 给 Storage Engine提供的接口实现了,不信的话继续看呗。。。! N/ i  f6 Q$ T2 T3 }5 D4 q( [7 {

; p8 {( e7 ?) A& G<3> 继续挖 ha_write_row; `" }- s7 h  U: x

* X' W$ e; ~0 F+ p3 ^int handler::ha_write_row(uchar *buf)
; g! q* J$ D+ @4 [1 f: [{
9 e# I" U9 n! L. l8 }- f    MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_WRITE_ROW, MAX_KEY, 0,{ error= write_row(buf); })  H5 k6 K- _' P) c1 p
}9 n) i% L3 M" w
" [6 a& L) t2 N! b
//这是一个虚方法
- M0 d: u5 t3 m; r! N) e) C5 ovirtual int write_row(uchar *buf __attribute__((unused)))8 H9 {9 r$ K) Z$ @& n" g4 B. W
{
1 Q2 k8 `& V5 a/ @3 n- A) J    return HA_ERR_WRONG_COMMAND;
# ^1 q+ k. L3 w6 ^* V, O6 m}; d0 O( y/ N  b
, S, }4 R3 c) N4 F. t( n2 h

; }% o: b  y7 _: O8 H9 {) y! s" J看到没有,write_row是个虚方法,也就是给底层方法实现的,在这里就是给各大Storage Engines的哈。&#128513;&#128513;&#128513;
4 C$ U3 ~0 w; j, r4 Y( ~- c) q/ y
9 y3 u6 C/ q3 z; N6 B: ~; B3. 调用链图5 e' w% U  e( G4 y8 f
这么多方法,看起来有点懵懵的吧,我来画一张图,帮助大家理解下这个调用堆栈。
. Q3 f4 D# {+ u7 l5 c  [8 [+ z4 C6 ]7 K% e" S
% B! j6 H' j! {2 L

/ d+ G- O2 Z, w* N9 u三:总结. L  n/ ]; O2 r# D
大家一定要熟读架构图,有了架构图从源码中找信息就方便多了,总之学习mysql成就感还是满满的。
; Z- E* p- }- {————————————————8 S1 u1 b$ {- v* `* g
版权声明:本文为CSDN博主「一线码农」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。0 m1 T( J% ]  l1 `/ P
原文链接:https://blog.csdn.net/huangxinchen520/article/details/106487415
6 Y9 U, l3 _! p




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