QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 3183|回复: 0
打印 上一主题 下一主题

[个人总经验] MySql轻松入门系列————第一站 从源码角度轻松认识mysql整体框架图

[复制链接]
字体大小: 正常 放大

326

主题

32

听众

1万

积分

  • TA的每日心情
    慵懒
    2020-7-12 09:52
  • 签到天数: 116 天

    [LV.6]常住居民II

    管理员

    群组2018教师培训(呼和浩

    群组2017-05-04 量化投资实

    群组2017“草原杯”夏令营

    群组2018美赛冲刺培训

    群组2017 田老师国赛冲刺课

    跳转到指定楼层
    1#
    发表于 2020-6-3 10:36 |只看该作者 |倒序浏览
    |招呼Ta 关注Ta
    一:背景9 ?0 p" i1 a0 q! Q9 @0 g3 W8 y3 E# L
    1. 讲故事
    " R( j. Y% E2 T最近看各大技术社区,不管是知乎,掘金,博客园,csdn基本上看不到有小伙伴分享sqlserver类的文章,看样子这些年sqlserver没落了,已经后继无人了,再写sqlserver是不可能再写了,这辈子都不会写了,只能靠技术输出mysql维持生活这样子。6 s4 O+ f3 Q1 r! q7 t
    ' G. i  B; ^# c& F' V
    二:了解架构图
    0 q. n; F' T- G/ f* Y5 y+ zmysql最大的好处就是开源, 手握百万源码,有什么问题搞不定呢? 这一点要比sqlserver爽多了,不用再dbcc捣来捣去。
    2 e4 E, d% l2 w6 n/ v7 o, Z' s& C7 f9 S
    1. 从架构图入手
    3 B3 j# B5 u" Q3 n7 ~+ t2 x. U大家都知道做/装修房子都要有一张图纸,其实软件也是一样,只要有了这么一张图纸,大方向就定下来了,再深入到细节也不会乱了方向,然后给大家看一下我自己画的架构图,画的不对请轻拍。+ D# k! ]* F6 U$ G0 Q' U: S$ N

      J4 @# F. |6 W* _8 U& S* u- Z7 q

    . |) t3 E! O) E3 I: a: J. ]其实SqlServer,Oracle,MySql架构都大同小异,MySql的鲜明特点就是存储引擎做成了插拔式,这就牛逼了,现行最常用的是InnoDB,这就让我有了一个想法,有一套业务准备用 InMemory 模式跑一下,厉害了~
    * _: h0 J7 `6 p7 M/ l0 b0 V( k/ X+ H% ?( \
    2. 功能点介绍
    3 B* n* _/ c: w/ MMySql其实就两大块,一块是MySql Server层,一块就是Storage Engines层。
    " z. s1 X4 j" Y2 E+ F$ j- P4 M6 a% |1 }5 t; C
    <1> Client  t; j5 p7 U; }2 k/ v9 q
    不同语言的sdk遵守mysql协议就可以与mysqld进行互通。& Z. {8 V% Y8 i$ k
    & P& c* c& W( l8 D; I4 B( _
    <2> Connection/Thread Pool8 b6 C% ]8 z' P: o# i9 P; {
    MySql使用C++编写,Connection是非常宝贵的,在初始化的时候维护一个池。
    * h  h) q' v0 R( j7 P
    " b' j6 k& B; `) i% O<3> SqlInterface,Parse,Optimizer,Cache# W+ t9 W7 U, w2 ?9 V- S
    对sql处理,解析,优化,缓存等处理和过滤模块,了解了解即可。* t( ?7 K8 {  F1 r, Z

    2 w7 R1 L! a1 M+ D& m3 ?2 C<4> Storage Engines+ j  A. K9 T3 J* y( f& F5 I9 ~7 ^
    负责存储的模块,官方,第三方,甚至是你自己都可以自定义实现这个数据存储,这就把生态做起来了,&#128046;&#128067;。
    & d/ U( c# h3 f" U1 v' c
    3 p% m5 `' A. H5 d1 x+ X三: 源码分析6 ?' g" {/ _& K
    关于怎么去下载mysql源码,这里就不说了,大家自己去官网捣鼓捣鼓哈,本系列使用经典的 mysql 5.7.14版本。/ y8 K! F( F: |* B
    : ^, {. @: @" }( Z" T% H5 q
    1. 了解mysql是如何启动监听的
    4 v, M1 y* w5 M7 n8 t1 n1 L; @手握百万行源码,怎么找入口函数呢??? &#128513;&#128513;&#128513;,其实很简单,在mysqld进程上生成一个dump文件,然后看它的托管堆不就好啦。。。* u3 j2 p- o5 j4 |$ z( c

    8 n2 U/ r- F! Q# @# C( a, n! ~; u6 b& ]0 j- r0 g! h4 H; Q' l: ]6 p
    3 C1 f. E. O. h1 {8 N# a% L
    从图中可以看到,入口函数就是 mysqld!mysqld_main+0x227 中的 mysqld_main, 接下来就可以在源码中全文检索下。" L7 R( J3 x; ^& p2 ]

    * p9 Y1 F3 z9 ?<1> mysqld_main 入口函数 => sql/main.cc- p( T3 f* _( K( b0 X; A

    # S: `3 p5 n' }) W7 w) W! r6 V1 R) S% S5 [4 X* i5 U1 t. m
    extern int mysqld_main(int argc, char **argv);& P7 J  F8 C5 T/ C/ I/ G9 z+ ?

    5 _, f$ r5 T" M$ i6 Xint main(int argc, char **argv)
    : H& I+ m/ S$ D1 [( I% H! W{( }. P7 M% l1 B# M" n7 s  y
      return mysqld_main(argc, argv);. H8 k. c: ?1 U8 l# B9 w
    }
    3 `7 `9 ~/ n: O) D4 ~2 K9 w
    6 @1 O6 r7 |! S3 V+ i( m
    ! Y" B* o" z/ Y' c8 _/ ?/ X6 A这里大家可以用visualstudio打开C++源码,使用查看定义功能,非常好用。. [' {$ u* ^4 [. x
    " q+ Y. u% I, E8 r( H. x6 {, a3 ~
    <2> 创建监听# E% \+ U( c6 f- E! _

    ; |/ s$ N* }' P4 d) n' z7 G2 s) E- {3 j3 x$ Q3 F+ |; \
    int mysqld_main(int argc, char **argv)
    2 i+ K) W5 I4 U9 a/ R& H{
    . z7 N) ^. i: r- P8 X% d    //创建服务监听线程
    , X% ~8 Y& K1 L; ?5 m) g5 |0 I    handle_connections_sockets();
    , x, A, H8 ^# O; P- y4 w( _0 L" Z}
      O) W' {$ m/ L  }
    9 A" n0 S' m* h* q# I7 N! jvoid handle_connections_sockets()
    1 ?6 p# R  D' \. H{
    . X5 l: `$ }6 J& F; j7 b  M3 I     //监听连接
    ; T( D+ Z% k7 j6 f     new_sock= mysql_socket_accept(key_socket_client_connection, sock,; ?$ Q0 W( f1 O  b
                                        (struct sockaddr *)(&cAddr), &length);& V+ \$ w9 J5 p+ a8 e* ]9 j

    ) Y+ Z4 n6 v7 M. u! B! P- w    if (mysql_socket_getfd(sock) == mysql_socket_getfd(unix_sock))
    % o6 M: c2 c9 m" Z      thd->security_ctx->set_host((char*) my_localhost);9 z' G' p3 d# f; D4 Y

    ! y. u" P6 T- |9 i% \    //创建连接
    + r* r  T5 X8 }6 }1 v) w( |    create_new_thread(thd);' I2 [# f/ s: @0 h, d- D7 H. j- X
    }
    + G5 N- a/ [- e7 M, s" m8 M: M. b" i& `" y+ Z5 J6 L
    //创建新线程处理处理用户连接# L0 I* @9 e* a" Y# ^, E0 _
    static void create_new_thread(THD *thd){# V7 y6 u/ n" W8 e" Z2 `
      v1 u2 j# V: d) U
       thd->thread_id= thd->variables.pseudo_thread_id= thread_id++;
    3 I% j: g7 q0 F# r5 @+ z5 n; C: }
    3 t6 P( @4 S" X( L7 A( Z9 ~1 ~   //线程进了线程调度器9 Y: A7 x! L8 ]6 U* b
       MYSQL_CALLBACK(thread_scheduler, add_connection, (thd));   ) M* v, W# [% E% j
    }
    2 ^( u0 t. s* O5 K( L+ E2 ^8 g+ ^/ N3 H/ R# A
    % O5 e1 M9 n/ g! m9 J  H& d
    至此mysql就开启了一个线程对 3306 端口进行监控,等待客户端请求触发 add_connection 回调。& |- C& H$ d0 {3 ]3 \' N

    6 a1 ]( F4 b/ m# z% G7 y4 N1 k( q7 z7 }3 M- U; n
    2. 理解mysql是如何处理sql请求- `0 j5 _# k  Z1 i/ e4 Q$ G
    这里我以Insert操作为例稍微解剖下处理流程:
    8 l+ l0 b% a) n0 }, i% v. e, |# M% e" p# o' C3 a7 v
    当用户有请求sql过来之后,就会触发 thread_scheduler的回调函数add_connection。8 f8 V# \4 Q7 n- B9 x
    ! Z$ w) g- P: e8 ^0 ]. m' R
    ) q4 [5 H" p( J* z6 \0 F& f
    static scheduler_functions one_thread_per_connection_scheduler_functions=1 f( t$ j  ?/ z; y
    {% O* L9 d& D8 ]6 C; d3 ~: u
      0,                                     // max_threads6 s# S! f% ]) v. c% }
      NULL,                                  // init( |+ j8 j  ]% ~: j1 w+ f
      init_new_connection_handler_thread,    // init_new_connection_thread7 |6 r9 k: |7 w/ J1 F
      create_thread_to_handle_connection,    // add_connection
    2 e/ T1 G; z% ~, r( J0 t  NULL,                                  // thd_wait_begin( a2 M, Y3 B" F4 C; i# F
      NULL,                                  // thd_wait_end
    9 q6 |  {' t' C% _  x- w  NULL,                                  // post_kill_notification
    & k5 ^" L5 }* n! ^  |+ t  one_thread_per_connection_end,         // end_thread
    5 u, ], `  l/ J% R  G  M# C  NULL,                                  // end9 r2 }9 u: F6 u, l" }
    };
    $ B5 Q. O' H! M, @5 j$ q5 M( D8 }

    ) Y% N2 ]1 A& h) P& b6 e) O从 scheduler_functions 中可以看到,add_connection 对应了 create_thread_to_handle_connection,也就是请求来了会触发这个函数,从名字也可以看出,用一个线程处理一个用户连接。
    " ^; W, f# ~4 E! y. u0 `7 x# g
    + d7 S+ w: w, }# Y8 M; S7 s<1> 客户端请求被 create_thread_to_handle_connection 接管及调用栈追踪3 P4 ^# R' f0 Z; X4 B; @9 d

    8 g7 L5 M: I8 \+ I3 s) R) B( I7 D- \void create_thread_to_handle_connection(THD *thd)
    * H4 m1 P! B# w0 n' T8 q, C{5 |8 A$ _. S6 v5 }! U9 x7 y
         if ((error= mysql_thread_create(key_thread_one_connection, &thd->real_id, &connection_attrib,% a/ X3 ~4 ]' f2 S0 J. @
                                         handle_one_connection,(void*) thd))){}& _) [8 b& K, }; w  u+ b
    }
    ) c  m7 g: o. y6 b$ _//触发回调函数  handle_one_connection9 l; M+ v- N4 M7 c
    pthread_handler_t handle_one_connection(void *arg)0 l2 L9 x9 o5 @5 ~8 @; z0 @
    {; v; F# P1 l2 q4 q
         do_handle_one_connection(thd);* C2 L1 i/ V" X) `# c6 T, R+ q
    }1 g3 B1 s- o; ~( v4 w+ y! d  W) g
    //继续处理
    # E- X3 w2 Q' s9 {void do_handle_one_connection(THD *thd_arg){$ C, O, V' M/ u* L7 u
        while (thd_is_connection_alive(thd))
    4 Z$ U9 Z8 L+ G! V8 Q    {
    ; ^. A0 L: n) O$ L' r      mysql_audit_release(thd);9 u2 C6 h9 H9 U, ?: s
          if (do_command(thd))  break;  //这里的 do_command 继续处理
    ) p/ D8 \) G( o7 r- W* Y: }5 E    }
    1 N* H. b' j' x4 q}$ I0 p0 P( I/ E  j9 {
    //继续分发/ ?3 s% A& i6 E
    bool do_command(THD *thd)" P8 R2 Y, m% `6 o. X  k4 Y0 i5 C
    {
    , p1 M" g- D8 E7 S: ?4 Z6 W( W5 a    return_value= dispatch_command(command, thd, packet+1, (uint) (packet_length-1));
    ) P, v" [- i. F2 Q}
    . [- M& H3 L: ]8 K: P/ x6 Ubool dispatch_command(enum enum_server_command command, THD *thd, char* packet, uint packet_length)
    % c$ a9 A5 e/ B! i5 y1 z{% n; M9 i$ q* ?
          switch (command) {! X( g& l. c+ S1 b0 H) x
             case COM_INIT_DB: ....  break;/ d9 L; P! r9 t& z5 f
             ...; H0 t  ~6 X  Q
             case COM_QUERY:   //查询语句:  insert xxxx
    ; c- s) s7 z  L* j* p             mysql_parse(thd, thd->query(), thd->query_length(), &parser_state);  //sql解析( b: t& m  y  j' l) e4 W+ T/ _! c
               break;
    2 Q" x4 A5 s3 j      }
    & D& y) w) N/ g5 F  D4 B/ J}2 ~, _" o, ]2 t( H' D0 K8 Y  w3 v
    //sql解析模块
    ' q& u2 H) m/ ^2 K1 E. J1 avoid mysql_parse(THD *thd, char *rawbuf, uint length, Parser_state *parser_state)% B& F; d7 V( n5 R! A+ d" h9 a
    {
    ; c2 m* b1 n) C* R' c      error= mysql_execute_command(thd);0 A/ C, ~5 J. b5 `* M7 E9 u
    }1 U% U. o! j1 G) c

    . z. F) s+ P3 W- n5 F
    7 i7 E3 w% U9 k. @) M<2> 到这里它的Parse,Optimizer,Cache都追完了,接下来看sql的CURD类型,继续追。。。
    + K; W/ k7 t4 P, p: S& r
    ( W% ?# T0 u8 s//继续执行
    2 L( r+ M. _) y- I* Z. T$ S, ^int mysql_execute_command(THD *thd)9 x( M3 X( R5 X( F8 q& ]
    {: Z% @; L0 m# Y! a3 E) T; d
      switch (lex->sql_command) ! d& S5 B" a2 E) b
      {
    $ ~7 u/ w9 ^0 J      case SQLCOM_SELECT:  res= execute_sqlcom_select(thd, all_tables);  break;
      l5 u% U0 W3 a7 Q
    8 s; K2 Z! N9 }  {+ K# l$ k; J, }4 g      //这个 insert 就是我要追的
    0 w9 K5 e, q. a      case SQLCOM_INSERT:   res= mysql_insert(thd, all_tables, lex->field_list, lex->many_values,
    " _$ F" T1 Q3 U9 ~                                              lex->update_list, lex->value_list,
    0 X2 t' D0 ~. s0 y" @1 i                                              lex->duplicates, lex->ignore);8 I' w, p  @( \7 L" U+ x6 w
      }
    8 ]2 p. Y8 }  y% ^& Z}' M7 E) i. f# M# D# [2 n, b
    //insert插入操作处理5 V3 j4 y( p  {$ D
    bool mysql_insert(THD *thd,TABLE_LIST *table_list,List<Item> &fields, List<List_item> &values_list,
    & q( S$ k/ D$ P. ^* e                  List<Item> &update_fields, List<Item> &update_values, 9 v! |/ {5 v6 a' Y  ]$ `$ X
                      enum_duplicates duplic, bool ignore)# n! Y6 a, u2 Q" ]! q
    {+ G7 A4 U5 I; d6 \
          while ((values= its++))( `4 k; P9 U9 Z
          {2 j9 H! C7 L6 s0 n5 w# A9 ^
               error= write_record(thd, table, &info, &update);" b/ h3 o& Q* S8 g. w
          }
    3 x, f* X6 |7 m+ d) w}
    0 b, v* b; h9 x8 W6 m/ F//写入记录, F  t4 Y8 G6 z& P( |* J& h6 Q
    int write_record(THD *thd, TABLE *table, COPY_INFO *info, COPY_INFO *update)* t6 i, k4 [7 G
    {2 ?- _  A: F. ?+ D" G! g) Z4 m
        if (duplicate_handling == DUP_REPLACE || duplicate_handling == DUP_UPDATE)
    , h) A( c$ A0 n' e- X6 d/ j    {8 u. q9 y6 X; E3 V! R/ x
             // ha_write_row  重点是这个函数
    $ T* H. y% D$ h$ K1 C         while ((error=table->file->ha_write_row(table->record[0])))/ B* Q. j% \/ C- C" {8 k1 n
             {
    + ]: J* \0 k; E* e% ?, V+ W# S4 o- E             ....6 r* Q1 o4 F; t+ E
             }
    9 J9 B  u% Z9 L# v    }" L! P9 e" p1 S6 F& y  F5 f
    }, U/ S8 n/ q# \* I! t1 F
    8 p( R7 [1 y6 s4 u1 N- Q6 E
    6 d! O* W, |( T) q! d
    7 ^% Q% b* _* N5 d
    可以看到,调用链还是挺深的,追到 ha_write_row 方法基本上算是追到头了,再往下的话就是 MySql Server 给 Storage Engine提供的接口实现了,不信的话继续看呗。。。
    - \+ Q/ w, w8 Z! o& r, I0 Z3 T1 i6 l8 U. Z6 s
    <3> 继续挖 ha_write_row
    $ S/ j! V; g7 p/ _+ z
    ; I! i$ r9 Y# m4 C0 F/ }1 bint handler::ha_write_row(uchar *buf)
    9 }( B' ]. ]* u- J{
    % ^" l4 \, X% a" \6 F! d    MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_WRITE_ROW, MAX_KEY, 0,{ error= write_row(buf); })* r4 o$ `; f0 W, \
    }
    " r( w$ R: A( z  r  M' c9 s( ?9 }1 z, g3 o% Q6 U0 l
    //这是一个虚方法
    9 g4 I+ q- o" |, R* `! l0 t! svirtual int write_row(uchar *buf __attribute__((unused)))( o: a1 R( ?/ _3 A  k; @
    {
    0 u: p# G0 N: Q7 |) o    return HA_ERR_WRONG_COMMAND;
    ) J: s* s& Y5 g0 c+ }0 u}
    : W% v7 o1 N/ q  g5 l* H! l, S8 r$ X/ m, r0 h1 \; ^
    & _  Y: v2 Q+ ~' G5 I
    看到没有,write_row是个虚方法,也就是给底层方法实现的,在这里就是给各大Storage Engines的哈。&#128513;&#128513;&#128513;5 P" i" q; t# j2 O# \
    & b9 K. U3 A* u7 @; R9 A& c2 F  x
    3. 调用链图
    5 Q* l) ^0 d/ h/ p这么多方法,看起来有点懵懵的吧,我来画一张图,帮助大家理解下这个调用堆栈。
    # V4 J) R  a" _7 \$ k8 A' \% c9 a5 E! D3 I: E: g) u
    % Z& J* [1 C9 y. R2 x

    3 k* V+ R" E1 @' m; k- v& d三:总结4 x" l. g% I. f! K$ S, p$ f
    大家一定要熟读架构图,有了架构图从源码中找信息就方便多了,总之学习mysql成就感还是满满的。% y- E% K4 b0 d5 P8 q$ i
    ————————————————
    & W" q; T  V% ]+ r. B$ S. B1 C版权声明:本文为CSDN博主「一线码农」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    ) s# s3 w/ U0 i$ l2 s. G2 i; G: D原文链接:https://blog.csdn.net/huangxinchen520/article/details/106487415, @; s) d1 C8 _5 y: k
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信
    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2025-11-19 02:59 , Processed in 0.695915 second(s), 50 queries .

    回顶部