深度解析MySQL启动时报“The server quit without updating PID file”错误的原因

时间:2022-08-29 10:30:43

很多童鞋在启动mysql的时候,碰到过这个错误,

首先,澄清一点,出现这个错误的前提是:通过服务脚本来启动mysql。通过mysqld_safe或mysqld启动mysql实例并不会报这个错误。

那么,出现这个错误的原因具体是什么呢?

哈哈,对分析过程不care的童鞋可直接跳到文末的总结部分~

总结

下面,来分析下mysql的服务启动脚本

脚本完整内容如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
#!/bin/sh
# Copyright Abandoned 1996 TCX DataKonsult AB & Monty Program KB & Detron HB
# This file is public domain and comes with NO WARRANTY of any kind
# MySQL daemon start/stop script.
# Usually this is put in /etc/init.d (at least on machines SYSV R4 based
# systems) and linked to /etc/rc3.d/S99mysql and /etc/rc0.d/K01mysql.
# When this is done the mysql server will be started when the machine is
# started and shut down when the systems goes down.
# Comments to support chkconfig on RedHat Linux
# chkconfig: 2345 64 36
# description: A very fast and reliable SQL database engine.
# Comments to support LSB init script conventions
### BEGIN INIT INFO
# Provides: mysql
# Required-Start: $local_fs $network $remote_fs
# Should-Start: ypbind nscd ldap ntpd xntpd
# Required-Stop: $local_fs $network $remote_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: start and stop MySQL
# Description: MySQL is a very fast and reliable SQL database engine.
### END INIT INFO
# If you install MySQL on some other places than /usr/local/mysql, then you
# have to do one of the following things for this script to work:
#
# - Run this script from within the MySQL installation directory
# - Create a /etc/my.cnf file with the following information:
# [mysqld]
# basedir=<path-to-mysql-installation-directory>
# - Add the above to any other configuration file (for example ~/.my.ini)
# and copy my_print_defaults to /usr/bin
# - Add the path to the mysql-installation-directory to the basedir variable
# below.
#
# If you want to affect other MySQL variables, you should make your changes
# in the /etc/my.cnf, ~/.my.cnf or other MySQL configuration files.
# If you change base dir, you must also change datadir. These may get
# overwritten by settings in the MySQL configuration files.
basedir=
datadir=
# Default value, in seconds, afterwhich the script should timeout waiting
# for server start.
# Value here is overriden by value in my.cnf.
# 0 means don't wait at all
# Negative numbers mean to wait indefinitely
service_startup_timeout=900
# Lock directory for RedHat / SuSE.
lockdir='/var/lock/subsys'
lock_file_path="$lockdir/mysql"
# The following variables are only set for letting mysql.server find things.
# Set some defaults
mysqld_pid_file_path=
if test -z "$basedir"
then
 basedir=/usr/local/mysql
 bindir=/usr/local/mysql/bin
 if test -z "$datadir"
 then
 datadir=/usr/local/mysql/data
 fi
 sbindir=/usr/local/mysql/bin
 libexecdir=/usr/local/mysql/bin
else
 bindir="$basedir/bin"
 if test -z "$datadir"
 then
 datadir="$basedir/data"
 fi
 sbindir="$basedir/sbin"
 libexecdir="$basedir/libexec"
fi
# datadir_set is used to determine if datadir was set (and so should be
# *not* set inside of the --basedir= handler.)
datadir_set=
#
# Use LSB init script functions for printing messages, if possible
#
lsb_functions="/lib/lsb/init-functions"
if test -f $lsb_functions ; then
 . $lsb_functions
else
 log_success_msg()
 {
 echo " SUCCESS! $@"
 }
 log_failure_msg()
 {
 echo " ERROR! $@"
 }
fi
PATH="/sbin:/usr/sbin:/bin:/usr/bin:$basedir/bin"
export PATH
mode=$1 # start or stop
[ $# -ge 1 ] && shift
other_args="$*" # uncommon, but needed when called from an RPM upgrade action
   # Expected: "--skip-networking --skip-grant-tables"
   # They are not checked here, intentionally, as it is the resposibility
   # of the "spec" file author to give correct arguments only.
case `echo "testing\c"`,`echo -n testing` in
 *c*,-n*) echo_n= echo_c=  ;;
 *c*,*) echo_n=-n echo_c=  ;;
 *)  echo_n= echo_c='\c' ;;
esac
parse_server_arguments() {
 for arg do
 case "$arg" in
  --basedir=*) basedir=`echo "$arg" | sed -e 's/^[^=]*=//'`
     bindir="$basedir/bin"
   if test -z "$datadir_set"; then
    datadir="$basedir/data"
   fi
   sbindir="$basedir/sbin"
   libexecdir="$basedir/libexec"
  ;;
  --datadir=*) datadir=`echo "$arg" | sed -e 's/^[^=]*=//'`
   datadir_set=1
 ;;
  --pid-file=*) mysqld_pid_file_path=`echo "$arg" | sed -e 's/^[^=]*=//'` ;;
  --service-startup-timeout=*) service_startup_timeout=`echo "$arg" | sed -e 's/^[^=]*=//'` ;;
 esac
 done
}
wait_for_pid () {
 verb="$1"   # created | removed
 pid="$2"   # process ID of the program operating on the pid-file
 pid_file_path="$3" # path to the PID file.
 i=0
 avoid_race_condition="by checking again"
 while test $i -ne $service_startup_timeout ; do
 case "$verb" in
  'created')
  # wait for a PID-file to pop into existence.
  test -s "$pid_file_path" && i='' && break
  ;;
  'removed')
  # wait for this PID-file to disappear
  test ! -s "$pid_file_path" && i='' && break
  ;;
  *)
  echo "wait_for_pid () usage: wait_for_pid created|removed pid pid_file_path"
  exit 1
  ;;
 esac
 # if server isn't running, then pid-file will never be updated
 if test -n "$pid"; then
  if kill -0 "$pid" 2>/dev/null; then
  : # the server still runs
  else
  # The server may have exited between the last pid-file check and now.
  if test -n "$avoid_race_condition"; then
   avoid_race_condition=""
   continue # Check again.
  fi
  # there's nothing that will affect the file.
  log_failure_msg "The server quit without updating PID file ($pid_file_path)."
  return 1 # not waiting any more.
  fi
 fi
 echo $echo_n ".$echo_c"
 i=`expr $i + 1`
 sleep 1
 done
 if test -z "$i" ; then
 log_success_msg
 return 0
 else
 log_failure_msg
 return 1
 fi
}
# Get arguments from the my.cnf file,
# the only group, which is read from now on is [mysqld]
if test -x ./bin/my_print_defaults
then
 print_defaults="./bin/my_print_defaults"
elif test -x $bindir/my_print_defaults
then
 print_defaults="$bindir/my_print_defaults"
elif test -x $bindir/mysql_print_defaults
then
 print_defaults="$bindir/mysql_print_defaults"
else
 # Try to find basedir in /etc/my.cnf
 conf=/etc/my.cnf
 print_defaults=
 if test -r $conf
 then
 subpat='^[^=]*basedir[^=]*=\(.*\)$'
 dirs=`sed -e "/$subpat/!d" -e 's//\1/' $conf`
 for d in $dirs
 do
  d=`echo $d | sed -e 's/[  ]//g'`
  if test -x "$d/bin/my_print_defaults"
  then
  print_defaults="$d/bin/my_print_defaults"
  break
  fi
  if test -x "$d/bin/mysql_print_defaults"
  then
  print_defaults="$d/bin/mysql_print_defaults"
  break
  fi
 done
 fi
 # Hope it's in the PATH ... but I doubt it
 test -z "$print_defaults" && print_defaults="my_print_defaults"
fi
#
# Read defaults file from 'basedir'. If there is no defaults file there
# check if it's in the old (depricated) place (datadir) and read it from there
#
extra_args=""
if test -r "$basedir/my.cnf"
then
 extra_args="-e $basedir/my.cnf"
else
 if test -r "$datadir/my.cnf"
 then
 extra_args="-e $datadir/my.cnf"
 fi
fi
parse_server_arguments `$print_defaults $extra_args mysqld server mysql_server mysql.server`
#
# Set pid file if not given
#
if test -z "$mysqld_pid_file_path"
then
 mysqld_pid_file_path=$datadir/`hostname`.pid
else
 case "$mysqld_pid_file_path" in
 /* ) ;;
 * ) mysqld_pid_file_path="$datadir/$mysqld_pid_file_path" ;;
 esac
fi
case "$mode" in
 'start')
 # Start daemon
 # Safeguard (relative paths, core dumps..)
 cd $basedir
 echo $echo_n "Starting MySQL"
 if test -x $bindir/mysqld_safe
 then
  # Give extra arguments to mysqld with the my.cnf file. This script
  # may be overwritten at next upgrade.
  $bindir/mysqld_safe --datadir="$datadir" --pid-file="$mysqld_pid_file_path" $other_args >/dev/null 2>&1 &
  wait_for_pid created "$!" "$mysqld_pid_file_path"; return_value=$?
  # Make lock for RedHat / SuSE
  if test -w "$lockdir"
  then
  touch "$lock_file_path"
  fi
  exit $return_value
 else
  log_failure_msg "Couldn't find MySQL server ($bindir/mysqld_safe)"
 fi
 ;;
 'stop')
 # Stop daemon. We use a signal here to avoid having to know the
 # root password.
 if test -s "$mysqld_pid_file_path"
 then
  mysqld_pid=`cat "$mysqld_pid_file_path"`
  if (kill -0 $mysqld_pid 2>/dev/null)
  then
  echo $echo_n "Shutting down MySQL"
  kill $mysqld_pid
  # mysqld should remove the pid file when it exits, so wait for it.
  wait_for_pid removed "$mysqld_pid" "$mysqld_pid_file_path"; return_value=$?
  else
  log_failure_msg "MySQL server process #$mysqld_pid is not running!"
  rm "$mysqld_pid_file_path"
  fi
  # Delete lock for RedHat / SuSE
  if test -f "$lock_file_path"
  then
  rm -f "$lock_file_path"
  fi
  exit $return_value
 else
  log_failure_msg "MySQL server PID file could not be found!"
 fi
 ;;
 'restart')
 # Stop the service and regardless of whether it was
 # running or not, start it again.
 if $0 stop $other_args; then
  $0 start $other_args
 else
  log_failure_msg "Failed to stop running server, so refusing to try to start."
  exit 1
 fi
 ;;
 'reload'|'force-reload')
 if test -s "$mysqld_pid_file_path" ; then
  read mysqld_pid < "$mysqld_pid_file_path"
  kill -HUP $mysqld_pid && log_success_msg "Reloading service MySQL"
  touch "$mysqld_pid_file_path"
 else
  log_failure_msg "MySQL PID file could not be found!"
  exit 1
 fi
 ;;
 'status')
 # First, check to see if pid file exists
 if test -s "$mysqld_pid_file_path" ; then
  read mysqld_pid < "$mysqld_pid_file_path"
  if kill -0 $mysqld_pid 2>/dev/null ; then
  log_success_msg "MySQL running ($mysqld_pid)"
  exit 0
  else
  log_failure_msg "MySQL is not running, but PID file exists"
  exit 1
  fi
 else
  # Try to find appropriate mysqld process
  mysqld_pid=`pidof $libexecdir/mysqld`
  # test if multiple pids exist
  pid_count=`echo $mysqld_pid | wc -w`
  if test $pid_count -gt 1 ; then
  log_failure_msg "Multiple MySQL running but PID file could not be found ($mysqld_pid)"
  exit 5
  elif test -z $mysqld_pid ; then
  if test -f "$lock_file_path" ; then
   log_failure_msg "MySQL is not running, but lock file ($lock_file_path) exists"
   exit 2
  fi
  log_failure_msg "MySQL is not running"
  exit 3
  else
  log_failure_msg "MySQL is running but PID file could not be found"
  exit 4
  fi
 fi
 ;;
 *)
  # usage
  basename=`basename "$0"`
  echo "Usage: $basename {start|stop|restart|reload|force-reload|status} [ MySQL server options ]"
  exit 1
 ;;
esac
exit 0

首先,定义相关参数

?
1
2
3
4
5
6
7
8
9
10
11
basedir=
datadir=
# Default value, in seconds, afterwhich the script should timeout waiting
# for server start.
# Value here is overriden by value in my.cnf.
# 0 means don't wait at all
# Negative numbers mean to wait indefinitely
service_startup_timeout=900
# Lock directory for RedHat / SuSE.
lockdir='/var/lock/subsys'
lock_file_path="$lockdir/mysql"

其中,

basedir 指的二进制压缩包解压后所在的目录,譬如/usr/local/mysql。

datadir 指的是数据目录

service_startup_timeout=900 定义mysql服务启动的时间限制,如果在900s中没有启动成功,则该脚本会退出。

?
1
lockdir='/var/lock/subsys'

关于/var/lock/subsys,网上的解释如下,后续会用到。

总的来说,系统关闭的过程(发出关闭信号,调用服务自身的进程)中会检查/var/lock/subsys下的文件,逐一关闭每个服务,如果某一运行的服务在/var/lock/subsys下没有相应的选项。在系统关闭的时候,会像杀死普通进程一样杀死这个服务。

通过察看/etc/rc.d/init.d下的脚本,可以发现每个服务自己操纵时都会去查看/var/lock/subsys下相应的服务。

很多程序需要判断是否当前已经有一个实例在运行,这个目录就是让程序判断是否有实例运行的标志,比如说xinetd,如果存在这个文件,表示已经有xinetd在运行了,否则就是没有,当然程序里面还要有相应的判断措施来真正确定是否有实例在运行。通常与该目录配套的还有/var/run目录,用来存放对应实例的PID,如果你写脚本的话,会发现这2个目录结合起来可以很方便的判断出许多服务是否在运行,运行的相关信息等等。 

判断basedir和datadir

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Set some defaults
mysqld_pid_file_path=
if test -z "$basedir"
then
 basedir=/usr/local/mysql
 bindir=/usr/local/mysql/bin
 if test -z "$datadir"
 then
 datadir=/usr/local/mysql/data
 fi
 sbindir=/usr/local/mysql/bin
 libexecdir=/usr/local/mysql/bin
else
 bindir="$basedir/bin"
 if test -z "$datadir"
 then
 datadir="$basedir/data"
 fi
 sbindir="$basedir/sbin"
 libexecdir="$basedir/libexec"
fi

其中,

mysqld_pid_file_path 指定pid文件的路径

-z string 判断字符串是否为空

如果basedir没有显示设置,则默认为/usr/local/mysql,这也是为什么很多mysql安装教程都推荐将mysql相关文件放到/usr/local/mysql下。

如果datadir没有显示设置,则默认为$basedir/data。

定义log_success_msg()和log_failure_msg()函数

首先,判断/lib/lsb/init-functions文件是否存在,如果存在,则使定义在init-functions文件中的所有shell函数在当前脚本中生效。

如果没有,则定义两个函数,一个用于打印成功日志,一个是打印错误日志。

在RHCS 6.7中,该文件并不存在,已被/etc/init.d/functions所替代。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#
# Use LSB init script functions for printing messages, if possible
#
lsb_functions="/lib/lsb/init-functions"
if test -f $lsb_functions ; then
 . $lsb_functions
else
 log_success_msg()
 {
 echo " SUCCESS! $@"
 }
 log_failure_msg()
 {
 echo " ERROR! $@"
 }
fi

传递参数

将第一个参数传递给mode,剩下的参数传递给other_args

?
1
2
3
4
5
6
7
8
9
10
11
12
13
PATH="/sbin:/usr/sbin:/bin:/usr/bin:$basedir/bin"
export PATH
mode=$1 # start or stop
[ $# -ge 1 ] && shift
other_args="$*" # uncommon, but needed when called from an RPM upgrade action
   # Expected: "--skip-networking --skip-grant-tables"
   # They are not checked here, intentionally, as it is the resposibility
   # of the "spec" file author to give correct arguments only.
case `echo "testing\c"`,`echo -n testing` in
 *c*,-n*) echo_n= echo_c=  ;;
 *c*,*) echo_n=-n echo_c=  ;;
 *)  echo_n= echo_c='\c' ;;
esac

解析配置文件中的参数

这个函数在脚本后面会涉及到。

主要涉及如下参数:--basedir,--datadir,--pid-file,--service-startup-timeout。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
parse_server_arguments() {
 for arg do
 case "$arg" in
  --basedir=*) basedir=`echo "$arg" | sed -e 's/^[^=]*=//'`
     bindir="$basedir/bin"
   if test -z "$datadir_set"; then
    datadir="$basedir/data"
   fi
   sbindir="$basedir/sbin"
   libexecdir="$basedir/libexec"
  ;;
  --datadir=*) datadir=`echo "$arg" | sed -e 's/^[^=]*=//'`
   datadir_set=1
 ;;
  --pid-file=*) mysqld_pid_file_path=`echo "$arg" | sed -e 's/^[^=]*=//'` ;;
  --service-startup-timeout=*) service_startup_timeout=`echo "$arg" | sed -e 's/^[^=]*=//'` ;;
 esac
 done
}

判断my_print_defaults的位置

首先,它判断当前路径下的bin目录中是否存在该可执行文件,如果不存在,则再判断$bindir(通常指的是$basedir/bin)目录下是否存在。

如果还是没有,则会判断/etc/my.cnf是否存在并且可读,如果是,则判断该配置文件中是否指定了basedir参数,

如果指定了,则取出该参数的值,并判断该值对应的目录中是否存在bin/my_print_defaults可执行文件

最后一步,如果在上述目录中实在没发现my_print_defaults文件,

索性就将print_defaults设置为"my_print_defaults",寄希望于该命令在当前的PATH环境中。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# Get arguments from the my.cnf file,
# the only group, which is read from now on is [mysqld]
if test -x ./bin/my_print_defaults
then
 print_defaults="./bin/my_print_defaults"
elif test -x $bindir/my_print_defaults
then
 print_defaults="$bindir/my_print_defaults"
elif test -x $bindir/mysql_print_defaults
then
 print_defaults="$bindir/mysql_print_defaults"
else
 # Try to find basedir in /etc/my.cnf
 conf=/etc/my.cnf
 print_defaults=
 if test -r $conf
 then
 subpat='^[^=]*basedir[^=]*=\(.*\)$'
 dirs=`sed -e "/$subpat/!d" -e 's//\1/' $conf`
 for d in $dirs
 do
  d=`echo $d | sed -e 's/[  ]//g'`
  if test -x "$d/bin/my_print_defaults"
  then
  print_defaults="$d/bin/my_print_defaults"
  break
  fi
  if test -x "$d/bin/mysql_print_defaults"
  then
  print_defaults="$d/bin/mysql_print_defaults"
  break
  fi
 done
 fi
 # Hope it's in the PATH ... but I doubt it
 test -z "$print_defaults" && print_defaults="my_print_defaults"
fi

查找默认的配置文件

-r file 如果文件可读,则为真

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#
# Read defaults file from 'basedir'. If there is no defaults file there
# check if it's in the old (depricated) place (datadir) and read it from there
#
extra_args=""
if test -r "$basedir/my.cnf"
then
 extra_args="-e $basedir/my.cnf"
else
 if test -r "$datadir/my.cnf"
 then
 extra_args="-e $datadir/my.cnf"
 fi
fi

解析配置文件中的参数

my_print_defaults的用法如下:

my_print_defaults --defaults-file=example.cnf client mysql

即读取配置文件中,client和mysql部分的参数配置,

具体在本脚本中,是读取mysqld,server,mysql_server,mysql.server四个部分的配置参数。

?
1
parse_server_arguments `$print_defaults $extra_args mysqld server mysql_server mysql.server`

设置pid file的路径

-z string 判断字符串是否为空

如果--pid-file没有在读取到的配置文件中设置或者脚本刚开始的mysqld_pid_file_path参数没有设置,

则pid file默认设置在datadir下,以主机名.pid命名。

如果该参数设置了,还需要进一步判断

如果该参数中带有斜杠,则代表给定的值带有路径,可直接使用。

如果该参数中没带路径,则代表给定的值只是pid的文件名,可将其设在datadir下。

?
1
2
3
4
5
6
7
8
9
10
11
12
#
# Set pid file if not given
#
if test -z "$mysqld_pid_file_path"
then
 mysqld_pid_file_path=$datadir/`hostname`.pid
else
 case "$mysqld_pid_file_path" in
 /* ) ;;
 * ) mysqld_pid_file_path="$datadir/$mysqld_pid_file_path" ;;
 esac
fi

服务脚本start选项

首先,切换到$basedir中

其次,判断$basedir/bin中的mysqld_safe是否是可执行文件,如果是,则启动mysqld实例,如果不是,则报错退出。

那么,启动流程又是如何实现的呢?

首先,执行$bindir/mysqld_safe --datadir="$datadir" --pid-file="$mysqld_pid_file_path" $other_args >/dev/null 2>&1 &命令,启动mysqld实例。

注意到没有,mysqld_safe其实是在basedir中执行的,包括mysql初始化脚本mysql_install_db,也建议在basedir中执行,具体可参考:

分析MariaDB初始化脚本mysql_install_db

然后通过wait_for_pid函数进行判断,具体可见下文对于wait_for_pid函数的分析

判断完毕后,

查看$lockdir目录是否可写,可写的话,则在目录上创建一个文件。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
case "$mode" in
 'start')
 # Start daemon
 # Safeguard (relative paths, core dumps..)
 cd $basedir
 echo $echo_n "Starting MySQL"
 if test -x $bindir/mysqld_safe
 then
  # Give extra arguments to mysqld with the my.cnf file. This script
  # may be overwritten at next upgrade.
  $bindir/mysqld_safe --datadir="$datadir" --pid-file="$mysqld_pid_file_path" $other_args >/dev/null 2>&1 &
  wait_for_pid created "$!" "$mysqld_pid_file_path"; return_value=$?
  # Make lock for RedHat / SuSE
  if test -w "$lockdir"
  then
  touch "$lock_file_path"
  fi
  exit $return_value
 else
  log_failure_msg "Couldn't find MySQL server ($bindir/mysqld_safe)"
 fi
 ;;

wait_for_pid函数

在利用mysqld_safe启动mysql实例后,会调用该参数

?
1
wait_for_pid created "$!" "$mysqld_pid_file_path"; return_value=$?

其中$!在shell中用于获取最后运行的后台Process的PID,具体在本例中,是mysqld_safe进程的pid。

因为第一个参数是created,所以会执行test -s "$pid_file_path" && i='' && break命令。

-s file 如果文件的长度不为零,则为真

该命令的意思是如果pid文件存在,则将变量i设置为空,并退出while循环。

然后执行如下判断,

?
1
2
3
4
5
6
7
if test -z "$i" ; then
 log_success_msg
 return 0
 else
 log_failure_msg
 return 1
 fi

如果$i为空,则打印成功日志,并退出脚本,很显然,在pid文件存在的情况下,会将变量i设置为空。

再来看看pid文件不存在的情况

首先,会判断$pid是否不为空(即if test -n "$pid")

如果不为空,则代表在执行完mysqld_safe后,已经捕捉到了该进程的pid。

在这种情况下,进一步通过kill -0 "$pid"确认该进程是否存在。

kill -0就是不发送任何信号,但是系统会进行错误检查,所以经常用来检查一个进程是否存在,当进程不存在时, kill -0 pid会返回错误

如果该进程存在,则不执行任何操作,直接跳到如下操作

?
1
2
3
echo $echo_n ".$echo_c"
i=`expr $i + 1`
sleep 1

将变量i加1,并sleep 1s。

然后,继续while循环,之所以这样做,是考虑到mysqld_safe已经执行,但是mysqld实例还在启动过程中,还没创建好pid文件。

一直到$1达到$service_startup_timeout定义的时长。

如果在while循环的过程中,通过kill -0 "$pid"判断到进程已经不存在了,

则会再判断一次,如果这次判断的结果依旧是pid file不存在,且进程不存在,则会执行

?
1
log_failure_msg "The server quit without updating PID file ($pid_file_path)."

这就是大名鼎鼎的“The server quit without updating PID file”的由来。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
wait_for_pid () {
 verb="$1"   # created | removed
 pid="$2"   # process ID of the program operating on the pid-file
 pid_file_path="$3" # path to the PID file.
 i=0
 avoid_race_condition="by checking again"
 while test $i -ne $service_startup_timeout ; do
 case "$verb" in
  'created')
  # wait for a PID-file to pop into existence.
  test -s "$pid_file_path" && i='' && break
  ;;
  'removed')
  # wait for this PID-file to disappear
  test ! -s "$pid_file_path" && i='' && break
  ;;
  *)
  echo "wait_for_pid () usage: wait_for_pid created|removed pid pid_file_path"
  exit 1
  ;;
 esac
 # if server isn't running, then pid-file will never be updated
 if test -n "$pid"; then
  if kill -0 "$pid" 2>/dev/null; then
  : # the server still runs
  else
  # The server may have exited between the last pid-file check and now.
  if test -n "$avoid_race_condition"; then
   avoid_race_condition=""
   continue # Check again.
  fi
  # there's nothing that will affect the file.
  log_failure_msg "The server quit without updating PID file ($pid_file_path)."
  return 1 # not waiting any more.
  fi
 fi
 echo $echo_n ".$echo_c"
 i=`expr $i + 1`
 sleep 1
 done
 if test -z "$i" ; then
 log_success_msg
 return 0
 else
 log_failure_msg
 return 1
 fi
}

服务脚本stop选项

首先,判断pid文件的长度是否不为零。

-s file 如果文件的长度不为零,则为真

此时,会通过pid文件获取mysqld进程的pid,注意,不是mysqld_safe进程的pid

然后,判断mysqld进程是否在正常运行,

如果是,则通过kill $mysqld_pid的方式来关闭mysqld进程

杀死进程最安全的方法是单纯使用kill命令,不加修饰符,不带标志。

标准的kill命令通常会终止有问题的进程,并把进程的资源释放给系统。然而,如果进程启动了子进程,只杀死父进程,子进程仍在运行,因此仍消耗资源。为了防止这些所谓的“僵尸进程”,应确保在杀死父进程之前,先杀死其所有的子进程。

然后,调用wait_for_pid函数进行判断,其实,wait_for_pid函数中设置avoid_race_condition变量的目的是为了stop选项,确实有可能出现,mysqld是在检查pid file之后,检查进程是否存活之前退出的。

如果mysqld进程没有正常运行,在会打印“MySQL server process #$mysqld_pid is not running!”信息,并删除pid文件。

如果在执行stop的时候,判断pid文件的长度为0,则会打印"MySQL server PID file could not be found!"信息。

所以,在pid文件不存在的情况下,通过服务脚本执行stop选项并不会关闭mysqld进程,这个时候,就可通过kill $mysqld_pid的方式来关闭mysqld进程。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
'stop')
 # Stop daemon. We use a signal here to avoid having to know the
 # root password.
 if test -s "$mysqld_pid_file_path"
 then
  mysqld_pid=`cat "$mysqld_pid_file_path"`
  if (kill -0 $mysqld_pid 2>/dev/null)
  then
  echo $echo_n "Shutting down MySQL"
  kill $mysqld_pid
  # mysqld should remove the pid file when it exits, so wait for it.
  wait_for_pid removed "$mysqld_pid" "$mysqld_pid_file_path"; return_value=$?
  else
  log_failure_msg "MySQL server process #$mysqld_pid is not running!"
  rm "$mysqld_pid_file_path"
  fi
  # Delete lock for RedHat / SuSE
  if test -f "$lock_file_path"
  then
  rm -f "$lock_file_path"
  fi
  exit $return_value
 else
  log_failure_msg "MySQL server PID file could not be found!"
 fi
 ;;

服务脚本restart选项

首先,先执行stop操作,如果stop操作成功的话,则继续执行start操作。

如果stop操作失败的话,则会输出"Failed to stop running server, so refusing to try to start."信息,并退出脚本。

?
1
2
3
4
5
6
7
8
9
10
'restart')
# Stop the service and regardless of whether it was
# running or not, start it again.
if $0 stop $other_args; then
 $0 start $other_args
else
 log_failure_msg "Failed to stop running server, so refusing to try to start."
 exit 1
fi
;;

服务脚本reload选项

首先,判断pid文件的长度是否为0,如果不为0,则将该文件中的值设置为mysqld_pid变量的值。

然后对该进程执行kill -HUP操作。

?
1
kill -HUP pid

pid 是进程标识。如果想要更改配置而不需停止并重新启动服务,请使用该命令。在对配置文件作必要的更改后,发出该命令以动态更新服务配置。

根据约定,当您发送一个挂起信号(信号 1 或 HUP)时,大多数服务器进程(所有常用的进程)都会进行复位操作并重新加载它们的配置文件。

如果pid文件的长度为0,则输出"MySQL PID file could not be found!"。

?
1
2
3
4
5
6
7
8
9
10
'reload'|'force-reload')
if test -s "$mysqld_pid_file_path" ; then
 read mysqld_pid < "$mysqld_pid_file_path"
 kill -HUP $mysqld_pid && log_success_msg "Reloading service MySQL"
 touch "$mysqld_pid_file_path"
else
 log_failure_msg "MySQL PID file could not be found!"
 exit 1
fi
;;

服务脚本status选项

首先,判断pid文件长度是否为0,如果不是,则读取该文件中的值,并判断pid对应的进程是否运行正常,

如果运行正常,则输出"MySQL running"

如果不正常,则输出"MySQL is not running, but PID file exists"

如果pid文件的长度为0,则试图通过mysqld的启动命令来获取其pid,

这个时候,可能存在一个mysqld程序启动了多个实例,这会导致pid_count=`echo $mysqld_pid | wc -w`大于1。

这个时候,会输出"Multiple MySQL running but PID file could not be found"信息,并退出脚本。

如果mysqld_pid为空,则会继续判断"$lock_file_path"是否存在,如果存在,

则会输出"MySQL is not running, but lock file ($lock_file_path) exists"信息。

如果"$lock_file_path"不存在,则会输出"MySQL is not running"信息。

如果mysqld_pid等于1,则会输出"MySQL is running but PID file could not be found"信息。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
'status')
# First, check to see if pid file exists
if test -s "$mysqld_pid_file_path" ; then
 read mysqld_pid < "$mysqld_pid_file_path"
 if kill -0 $mysqld_pid 2>/dev/null ; then
 log_success_msg "MySQL running ($mysqld_pid)"
 exit 0
 else
 log_failure_msg "MySQL is not running, but PID file exists"
 exit 1
 fi
else
 # Try to find appropriate mysqld process
 mysqld_pid=`pidof $libexecdir/mysqld`
 # test if multiple pids exist
 pid_count=`echo $mysqld_pid | wc -w`
 if test $pid_count -gt 1 ; then
 log_failure_msg "Multiple MySQL running but PID file could not be found ($mysqld_pid)"
 exit 5
 elif test -z $mysqld_pid ; then
 if test -f "$lock_file_path" ; then
  log_failure_msg "MySQL is not running, but lock file ($lock_file_path) exists"
  exit 2
 fi
 log_failure_msg "MySQL is not running"
 exit 3
 else
 log_failure_msg "MySQL is running but PID file could not be found"
 exit 4
 fi
fi
;;

服务脚本其它选项

如果脚本的第一个参数不是上述几个选项,则会输出Usage信息。

?
1
2
3
4
5
6
*)
 # usage
 basename=`basename "$0"`
 echo "Usage: $basename {start|stop|restart|reload|force-reload|status} [ MySQL server options ]"
 exit 1
;;

至此,mysql的服务脚本分析完毕~

总结

在通过服务脚本启动mysql的过程中,报“The server quit without updating PID file”错误,有两个条件

首先,pid文件不存在

其次,通过kill -0 $pid检查到进程并不存在

这个时候,只能通过mysql数据库的错误日志来定位。

服务脚本如果不做任何调整的话,默认的basedir是/usr/local/mysql,datadir是/usr/local/mysql/data

如果自己的mysql服务均不是默认路径,

则需要在该脚本中显式设置

经测试,需设置如下几处:

1. 设置basedir和添加conf变量

其中,conf指的是mysqld的配置文件,建议配置文件中显式指定basedir和datadir的值。

在这里,datadir可不设置,因为datadir可通过配置文件来获取。

但是basedir必须要指定,因为要首先根据basedir来判断my_print_deefauts命令

?
1
2
3
basedir=/usr/local/mysql-advanced-5.6.23-linux-glibc2.5-x86_64
datadir=
conf=/usr/local/mysql-advanced-5.6.23-linux-glibc2.5-x86_64/my_3308.cnf

2. 第256行,添加extra_args=" -c $conf"

?
1
2
3
4
5
6
7
8
9
10
11
extra_args=" -e $basedir/my.cnf.bak"
if test -r "$basedir/my.cnf"
then
 extra_args="-e $basedir/my.cnf"
else
 if test -r "$datadir/my.cnf"
 then
 extra_args="-e $datadir/my.cnf"
 fi
fi
extra_args=" -c $conf"

3. 修改285行mysqld_safe的启动参数

?
1
$bindir/mysqld_safe --datadir="$datadir" --pid-file="$mysqld_pid_file_path" $other_args >/dev/null 2>&1 &

修改为,

?
1
$bindir/mysqld_safe --defaults-file="$conf" --datadir="$datadir" --pid-file="$mysqld_pid_file_path" $other_args >/dev/null 2>&1 &

主要是添加了--defaults-file选项

以上所述是小编给大家介绍的深度解析MySQL启动时报“The server quit without updating PID file”错误的原因,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对服务器之家网站的支持!

原文链接:http://www.cnblogs.com/ivictor/p/6846017.html