- 在线时间
- 661 小时
- 最后登录
- 2023-8-1
- 注册时间
- 2017-5-2
- 听众数
- 32
- 收听数
- 1
- 能力
- 10 分
- 体力
- 55572 点
- 威望
- 51 点
- 阅读权限
- 255
- 积分
- 17623
- 相册
- 0
- 日志
- 0
- 记录
- 0
- 帖子
- 447
- 主题
- 326
- 精华
- 1
- 分享
- 0
- 好友
- 79
TA的每日心情 | 慵懒 2020-7-12 09:52 |
|---|
签到天数: 116 天 [LV.6]常住居民II 管理员
 群组: 2018教师培训(呼和浩 群组: 2017-05-04 量化投资实 群组: 2017“草原杯”夏令营 群组: 2018美赛冲刺培训 群组: 2017 田老师国赛冲刺课 |
一:背景
1 i5 r9 d2 G6 g. v3 f: s1. 讲故事, [# @4 C+ d# Q& }) q
最近看各大技术社区,不管是知乎,掘金,博客园,csdn基本上看不到有小伙伴分享sqlserver类的文章,看样子这些年sqlserver没落了,已经后继无人了,再写sqlserver是不可能再写了,这辈子都不会写了,只能靠技术输出mysql维持生活这样子。$ {( R" e$ L2 w7 C/ G/ n& T
8 D! L* [$ L# e. {9 p二:了解架构图. w6 z' X) R5 ~2 \ W6 y
mysql最大的好处就是开源, 手握百万源码,有什么问题搞不定呢? 这一点要比sqlserver爽多了,不用再dbcc捣来捣去。
7 \+ T+ c* b% ]; m% g' X2 _' N8 T! X8 V n- [6 k
1. 从架构图入手2 S {; w, Z" n$ W9 @7 Y
大家都知道做/装修房子都要有一张图纸,其实软件也是一样,只要有了这么一张图纸,大方向就定下来了,再深入到细节也不会乱了方向,然后给大家看一下我自己画的架构图,画的不对请轻拍。
3 v' V2 L' K- R5 B. a: n- u" ]4 I![]()
9 i' {7 @* j( J1 B5 V
& S) k9 z/ Q; d- s. N, g: r* X& F8 M9 z G5 |6 l) D
其实SqlServer,Oracle,MySql架构都大同小异,MySql的鲜明特点就是存储引擎做成了插拔式,这就牛逼了,现行最常用的是InnoDB,这就让我有了一个想法,有一套业务准备用 InMemory 模式跑一下,厉害了~
/ `* [! g z& h3 e5 w% U: W9 H
9 N- e1 I1 u7 J t( U% j7 P2. 功能点介绍
9 ~4 V: ~0 E2 `MySql其实就两大块,一块是MySql Server层,一块就是Storage Engines层。
) s* r: d. q5 b7 c# ]% i# C" t- x. c! D- Z4 Z: m
<1> Client
) _8 y: J L' f6 i不同语言的sdk遵守mysql协议就可以与mysqld进行互通。/ A" E% z& h" v, m% R
9 o6 z' M/ q+ w- M
<2> Connection/Thread Pool6 `& I, P. `8 p
MySql使用C++编写,Connection是非常宝贵的,在初始化的时候维护一个池。% L& V# P$ ]! }3 Y3 k" F
% |; f, T5 J$ b+ O6 ~: i: R
<3> SqlInterface,Parse,Optimizer,Cache, g( O" A$ Y+ b$ l6 w/ x4 v4 Y+ P. n( ^
对sql处理,解析,优化,缓存等处理和过滤模块,了解了解即可。
8 _: N$ p9 _- a8 ^* C+ H: c- c0 p& ?( I% [( ^7 V
<4> Storage Engines p. A2 q1 I! E0 U6 F
负责存储的模块,官方,第三方,甚至是你自己都可以自定义实现这个数据存储,这就把生态做起来了,🐮👃。
0 j4 W) I( u. O5 e% \8 a
6 G# k8 ]- O, ~* h% ?三: 源码分析
: F" t9 |; i0 J/ b0 ]关于怎么去下载mysql源码,这里就不说了,大家自己去官网捣鼓捣鼓哈,本系列使用经典的 mysql 5.7.14版本。
0 x" D- \* |! V8 x# f( K( l0 }5 h% k6 M7 c; ?
1. 了解mysql是如何启动监听的
* C+ q( r% P5 C7 o8 t手握百万行源码,怎么找入口函数呢??? 😁😁😁,其实很简单,在mysqld进程上生成一个dump文件,然后看它的托管堆不就好啦。。。
& L5 v! t' k S' o/ \" J9 i3 @ 5 W* @6 E: h9 M9 v
0 ?2 R: `& u' i2 E' o9 ?5 P
0 M, N8 P7 C$ [从图中可以看到,入口函数就是 mysqld!mysqld_main+0x227 中的 mysqld_main, 接下来就可以在源码中全文检索下。& K4 B0 v* t5 n: P" u5 Q3 F8 F
: M) ^! K- {* ]$ U( r$ Z2 b<1> mysqld_main 入口函数 => sql/main.cc
& j% K3 N5 L4 g' K0 M9 `* K; Q# T5 a: D
2 u' n6 {! a; O0 \
extern int mysqld_main(int argc, char **argv);* n: u" O$ q6 I$ x0 A
9 O' |, V) l. m+ G; i" a
int main(int argc, char **argv)
2 |5 a0 n, Q% }8 e+ \7 S! F, ~, S{: p6 Y% I. k7 ~
return mysqld_main(argc, argv);; y. x) ~& k- A5 m# S
}
0 w5 D K4 ]5 a# v- Y" D& w1 r& _ @1 q+ Z: l
5 I2 x8 @ ~7 Y/ {7 u
这里大家可以用visualstudio打开C++源码,使用查看定义功能,非常好用。
* g0 V/ t4 f9 i
2 I/ [0 j" g4 J- z, M. c. K0 Z<2> 创建监听
- f/ o0 O" i0 i( }7 _2 R+ L
* W2 j+ n" o; \. m. O8 [5 q; s) Q* v% k0 h: F
int mysqld_main(int argc, char **argv); H8 Q* w2 U* }6 s, G
{* R o: G; ~3 ^& _6 C8 J: x
//创建服务监听线程
' F: |: j: N: y3 I handle_connections_sockets();
1 f- h) ~; ~. ~: T: i$ a! s0 U, G. F}
9 F7 f7 j5 `6 e$ {
! }7 h& e; N# l0 xvoid handle_connections_sockets()
! T1 t* V' g! T$ N) R1 e{4 m! D" O4 U: ]' K" [
//监听连接
6 g; ^' |2 C4 f3 b# u2 x( V new_sock= mysql_socket_accept(key_socket_client_connection, sock,
$ o5 j: H+ T0 s; R (struct sockaddr *)(&cAddr), &length);
. H6 r+ i( q; }. Z& _1 I+ F# Y ]2 H( Z2 z
if (mysql_socket_getfd(sock) == mysql_socket_getfd(unix_sock))) |- Y5 N) M' f6 W
thd->security_ctx->set_host((char*) my_localhost);( f1 E& d: q0 R
8 j0 L; A7 l. @9 `6 ^ //创建连接1 s2 G6 O9 u* ^
create_new_thread(thd);
" f$ t* L* Z6 P% h: h6 V1 m% L}8 [# \* ]- W6 f( ~: |6 ?2 H
/ Y, H8 `& i; G; W
//创建新线程处理处理用户连接
9 G2 W9 q+ \7 `. K* w% D0 s3 ~static void create_new_thread(THD *thd){% m* e9 g. S4 O/ ?( Z* ]" d, E
$ g# P9 X& t6 x& D8 `0 J thd->thread_id= thd->variables.pseudo_thread_id= thread_id++;' Q: y( e. U% D
0 e( V; M$ t' {5 c2 k: h //线程进了线程调度器
" K+ i+ @0 x# |3 _2 L; k# n2 o9 D MYSQL_CALLBACK(thread_scheduler, add_connection, (thd));
, k- a8 [7 V' m) O}+ u" Z( M( V. t5 W/ {
+ U8 L9 O8 B( L# J( I- v \
6 V* _2 M( @( e至此mysql就开启了一个线程对 3306 端口进行监控,等待客户端请求触发 add_connection 回调。
0 S4 O! D6 K {: E! h) b/ C l7 T7 {; G) w8 Y8 P' o+ z
- `, Z5 j. F- |2 n5 s9 f, \& e; ]2. 理解mysql是如何处理sql请求8 Q; R/ e9 s5 m7 m J/ X
这里我以Insert操作为例稍微解剖下处理流程:( }) b& l8 { x2 E
+ ], a y1 X4 r6 r
当用户有请求sql过来之后,就会触发 thread_scheduler的回调函数add_connection。
; k& _& P2 M G6 |$ _/ ?- D
8 o+ }* H3 \# K( p) b
% J/ z/ F' H6 |; Y3 Mstatic scheduler_functions one_thread_per_connection_scheduler_functions=
# k; m+ p" t0 I# B{
. q2 |9 |6 b5 @( S! R# r/ t 0, // max_threads. B( J( r& g5 Z7 f" Z4 m
NULL, // init
/ H9 s. i7 A, ^0 ~ init_new_connection_handler_thread, // init_new_connection_thread8 u9 z# w( `; t$ o6 Y8 ?
create_thread_to_handle_connection, // add_connection
/ g; g+ {4 l9 k) @, h NULL, // thd_wait_begin
. h. J. a3 B& }) w R6 h/ | NULL, // thd_wait_end
: n' p2 }; g+ Y( s- S' B9 F NULL, // post_kill_notification
/ x( O$ z+ ~& `" W/ j/ g: q, d7 G one_thread_per_connection_end, // end_thread4 a1 ^ F" a$ n5 A3 z8 T2 b
NULL, // end
& W# F9 X/ \ m- s+ m" t' i};
}% ]2 H6 n8 J; I) z) g
$ v. l; h6 e* W; j0 w, o, }' m7 E1 K& J/ y8 ]( w: [
从 scheduler_functions 中可以看到,add_connection 对应了 create_thread_to_handle_connection,也就是请求来了会触发这个函数,从名字也可以看出,用一个线程处理一个用户连接。, I0 Y% I9 g% t) B
; Y z- h1 U8 l$ W9 p
<1> 客户端请求被 create_thread_to_handle_connection 接管及调用栈追踪
" n* g6 A" \ @$ b
% V' v) B- Z4 F, mvoid create_thread_to_handle_connection(THD *thd); X9 M% }$ s2 @( J# N( e5 {6 Z
{8 o l( V" X K# V. s
if ((error= mysql_thread_create(key_thread_one_connection, &thd->real_id, &connection_attrib,/ M7 [5 F4 @; G3 O8 l
handle_one_connection,(void*) thd))){}
( L* X0 t* ?3 Z6 V}
$ W$ M$ w3 k7 H6 Z* A' j//触发回调函数 handle_one_connection1 x0 T/ y2 ?. y8 `
pthread_handler_t handle_one_connection(void *arg)
" W6 ~5 E2 l k{; [; ]+ Y6 m5 Q8 D8 I1 f7 R
do_handle_one_connection(thd);; P/ b: j/ _' [0 Q) c9 ?
}
- c6 o3 y1 Q5 K# z1 n2 N//继续处理1 c0 v+ \& w2 Z W0 c3 Q- Z
void do_handle_one_connection(THD *thd_arg){& g+ {" Z# e6 c6 B0 @
while (thd_is_connection_alive(thd))( i1 A3 A/ Z: t
{9 V N3 E4 o; h, _, }6 J9 l( `. e
mysql_audit_release(thd);; o2 Y9 Z' B- s* Y, |4 E1 i8 F; @
if (do_command(thd)) break; //这里的 do_command 继续处理+ v3 p# s* d# M/ s8 n
}
- A6 u2 u2 @6 v2 I% y/ C: M Q}
$ Q% H. {" p# W+ u//继续分发
0 u" B8 H1 j7 y2 U1 K$ sbool do_command(THD *thd)5 y+ D- |' m, {/ M( q% Q/ \* I
{- s% y3 f9 |' y- ^3 e9 I
return_value= dispatch_command(command, thd, packet+1, (uint) (packet_length-1));
, C& D* F% T0 [, ~+ n}
5 z9 o+ Y' p' v5 t5 N M. i+ j1 ubool dispatch_command(enum enum_server_command command, THD *thd, char* packet, uint packet_length) z% i4 u' u: P0 d6 @$ j
{
# d) K" D0 L/ \" Y' ^# B- { switch (command) {5 F# m" v: d) Y! \+ J
case COM_INIT_DB: .... break;
* A# W r4 _, l- R; U4 }' e, A ...! c2 p! D: S9 ?, u! b! m3 X( T7 {
case COM_QUERY: //查询语句: insert xxxx- l- C7 R2 \" x3 N
mysql_parse(thd, thd->query(), thd->query_length(), &parser_state); //sql解析
; } y; I' z }8 v0 X break;
) Q+ Z0 z7 S, O; [4 n, d }: [' \/ p5 E( D6 K4 ~5 Z
}! q- ~% F7 N% O0 ?
//sql解析模块
* |, Y9 ?2 e. U0 Rvoid mysql_parse(THD *thd, char *rawbuf, uint length, Parser_state *parser_state)
2 ^: k9 F$ n+ j+ o6 N& g; F{4 v, Y) [8 @9 B/ N& \' T& s, j' G
error= mysql_execute_command(thd);; ]. @ k/ r5 s/ `
}
* p0 D' P1 v0 g5 x$ }2 s# a. f) v9 B7 e" T6 A1 N
, ? c# L; f7 c" i3 M3 j7 G
<2> 到这里它的Parse,Optimizer,Cache都追完了,接下来看sql的CURD类型,继续追。。。) U* d9 O& f$ k$ t( I, |% N- T' m5 `1 C
0 ?0 A% a. ~: o/ H//继续执行
* G8 ~1 S0 V# F1 w7 uint mysql_execute_command(THD *thd)7 h5 }" [6 U2 k7 _
{
2 r7 z5 h6 ~4 p5 \/ d: l switch (lex->sql_command) ! P/ E$ O. u# d4 q
{
5 }5 W1 a4 W/ s, D3 [4 r case SQLCOM_SELECT: res= execute_sqlcom_select(thd, all_tables); break;
8 q8 B% N. x/ {* L! g2 t3 l/ U; W' R! ?5 g
//这个 insert 就是我要追的
) Q8 g: D: f* h* V1 l A case SQLCOM_INSERT: res= mysql_insert(thd, all_tables, lex->field_list, lex->many_values,# \; k4 b4 U0 u- t
lex->update_list, lex->value_list,' u2 e0 F2 P4 C \, Z) o$ c
lex->duplicates, lex->ignore);
3 F! c) ^; D. {8 {* q' a- D }
! N D2 ?8 {( g/ m0 R5 B}
7 z. c+ }2 r+ ?4 A0 G# M//insert插入操作处理2 ^- P( [( c. E: L+ Z
bool mysql_insert(THD *thd,TABLE_LIST *table_list,List<Item> &fields, List<List_item> &values_list,1 A+ p2 P! C5 ~. A2 R
List<Item> &update_fields, List<Item> &update_values,
7 R7 {, |- O h8 l: j* q enum_duplicates duplic, bool ignore), I' l6 g& ~' [6 i0 l
{ y% a7 `& f: z4 m
while ((values= its++)); e7 c* R3 a n
{
- W1 K8 p, d/ y/ W8 U0 Q% O error= write_record(thd, table, &info, &update);
# B4 q3 U- d. w1 g+ U [% Q, j }0 i" T; W. R( n" o: |$ i9 Y
}
- q7 E7 T1 L4 ]( `1 A' @//写入记录
6 w8 a# b9 B1 K7 W5 M O' Fint write_record(THD *thd, TABLE *table, COPY_INFO *info, COPY_INFO *update)
4 x/ g. F" P4 {( W h( r* h{6 H/ N! z' v1 O: R
if (duplicate_handling == DUP_REPLACE || duplicate_handling == DUP_UPDATE)
1 V6 ?) j" N1 b* G1 k7 j( S& [# C" p {+ H6 X, b7 h! k8 E" m/ v
// ha_write_row 重点是这个函数
7 m1 p$ z9 R: h; K+ e while ((error=table->file->ha_write_row(table->record[0])))2 L8 z. ?+ @6 w0 @8 V b% [
{
# Z0 s( Z5 i+ C4 e/ K% D7 v ....
3 Q/ V2 j, [ S! i$ r }
/ P# Q3 W5 p N1 b' C }
6 c) u9 J; d0 Z- g}
* y( F* u8 L8 [9 E
8 v+ Z7 R2 l2 J* E4 @
x9 F6 H, j0 }: p1 U' ?- t0 [& S9 p2 [, ]
可以看到,调用链还是挺深的,追到 ha_write_row 方法基本上算是追到头了,再往下的话就是 MySql Server 给 Storage Engine提供的接口实现了,不信的话继续看呗。。。4 G- j4 _+ C; k
$ V) h% N% r" q+ J3 U, R- G8 T: {
<3> 继续挖 ha_write_row
7 E* G) R5 |$ c8 z- ^$ |/ g2 @2 ^/ X+ _
int handler::ha_write_row(uchar *buf)) L0 z# S1 g; J6 v' |
{3 H" _& A% D9 {# v5 i+ K7 d4 b
MYSQL_TABLE_IO_WAIT(m_psi, PSI_TABLE_WRITE_ROW, MAX_KEY, 0,{ error= write_row(buf); })* ]. @/ e/ c9 B8 t
}
- n n" k/ z2 j% U& C* P' u1 L
7 |; s. S; l4 R* h5 V; B//这是一个虚方法
6 i( S0 h, J5 t8 z2 F, hvirtual int write_row(uchar *buf __attribute__((unused)))% n/ l! U W) [
{
; L( ~- R4 J+ Q/ I return HA_ERR_WRONG_COMMAND;+ A! r# j& _+ x# s! ~4 E
}
# |1 J- _3 X! n3 r
: b" c9 }3 b% M" k! Q) @9 [
& \, r/ j; V* b) A& g5 ^ N. s; ~% ]看到没有,write_row是个虚方法,也就是给底层方法实现的,在这里就是给各大Storage Engines的哈。😁😁😁' F" ` m. z% `6 Q( l0 b' r- D
; v- e/ d' X$ G8 f% |* }) n
3. 调用链图
! x+ e8 J' f4 U这么多方法,看起来有点懵懵的吧,我来画一张图,帮助大家理解下这个调用堆栈。* o8 Y" M u9 \5 U
![]()
# j4 \0 c5 ?6 e" s2 X' A. f
4 `+ w, t+ p2 |
4 ?3 q' I- ?( [# P三:总结
& u- w* E9 b4 X, C大家一定要熟读架构图,有了架构图从源码中找信息就方便多了,总之学习mysql成就感还是满满的。
* N1 v( ~& R: ^5 z) x2 N————————————————
; ^0 { F% e# Y版权声明:本文为CSDN博主「一线码农」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
" U/ C4 U6 @( X$ s原文链接:https://blog.csdn.net/huangxinchen520/article/details/1064874153 I- {/ ~& |$ R) M# V$ ?8 l& W
|
zan
|