记录一次sql优化

一大早到公司,加班一宿的同事就跟我抱怨,说,一个表只有8w条数据,需要更新一列,根据目前进度估算,大概需要3个小时,即使不需要更新,查询每条数据也需要400ms左右;

或许确实配置太低,或许确实表的字段太多,我并没有过于吃惊,但是3小时确实太多,便一起查了一下:

  1. iops并不高,说明内存够用
  2. cpu很高,很可能没有合适的索引,说是用了索引了,还是确认一下吧
  3. 果然搞错了,没有用到索引
  4. 8w条数据,添加索引也应该很快,于是添加了一个索引,耗时不到10s
  5. 执行sql语句,还是很慢,explain显示可能能用到我们刚才添加的索引,但是rows上来看还是全表扫描,为什么?
  6. sql语句大致为: update table1 set bb=1 where aa=123
  7. 现在已经基本可以怀疑aa的类型应该不是int的了,查看表结构,果然aa的类型为varchar
  8. 修改sql语句:update table1 set bb=1 where aa=’123′
  9. 重新开始更新8w条数据,共耗时不到10s

分析:

因为aa是varchar类型,如果要和一个数字比较,势必要对aa进行转换,如此就用不到索引了;这种错误太容易出现了

用shell试图使用大量内存的方法

一条命令:

如果要持续占用一段时间呢?

能否把1G的数据保存到shell的变量中呢?

测试是不行的,可能是太大了吧

关于docker-runc

docker命令行会有一系列的操作容器的方法,如: create 、run、start、exec等等,这些都是通过docker daemon的sock文件由docker daemon实现的,那么docker daemon又是如何实现的呢?

docker-runc就是最终执行的程序,docker-runc也提供了一系列类似于docker的操作,如: create、start、run、exec等,当然,也不完全一样;docker-runc  exec的时候可以指定环境变量、进程的能力等,所以,我们可以直接使用docker-runc来在已有的容器内部执行命令,需要注意的是,docker create时定义的环境变量需要通过选项指定,因为docker-runc不知道这些(奇怪的是,预定义的进程的能力似乎是知道的)

关于容器中进程的继承关系

下图是一个容器中的进程:

  1. 每个容器总是有一个1号进程
  2. 1号进程的父进程在容器中显示为0号进程,而在容器外表现为进程 docker-containerd-shim
  3. 容器中的1号进程不同于宿主机的1号进程那么NB,宿主机的1号进程直接或间接地领导所有其它进程;而容器中的1号进程并不一定直接或间接地领导所有进程,取决于这些进程是否1号进程来创建的,这些进程的父进程死掉后会最终归属到容器内所谓的0号进程而非1号进程,不过,容器中所谓的0号进程并非都是同一个进程
  4. docker top 显示的容器中的进程可能不太全,与是否该进程归属于1号进程没有任何关系;与进程是否最终归属于该容器的管理进程docker-containerd-shim也没有关系,如果是nsenter进入容器,则启动的进程在docker top中是看不到的,虽然该进程在容器中显示的ppid也是0,其实同样是0的ppid却可能不是同一个进程,因为,只要父进程在容器外部,则容器内部显示的ppid就统一为0; 为什么docker top可能看到的不全?docker top是如何实现的呢?参看: https://phpor.net/blog/post/4420

关于PHP Fatal error时获取更多信息

PHP 输出错误时的堆栈情况:

如果需要添加更多的信息,只需要在php_error_cb 函数里面添加就行了

关于C语言之字符串拼接(堆与栈)

且看一个demo:

问题: 如何在errstr信息前面添加一个HOSTNAME 环境变量信息?

  1. 考虑到errstr有可能写到标准错误,也有可能写到标准输出,也有可能根本就不输出,所以,提前输出HOSTNAME 信息显然不合适
  2. 考虑到引用errstr的地方确实不少,最好一开始就拼接HOSTNAME到该errstr 上面去,那么如何拼接呢?

如果是在脚本语言或者带有gc的语言中,完全可以直接在errstr变量前面拼接那个动态获取到的环境变量,如:

但是c中却不能这样,为什么呢?字符串拼接的函数不是有的吗,如:strcat;

事实却并非如此简单,为什么呢?

上面的errstr变量是存储在栈上的,不需要考虑free的问题,原作者考虑到后续一大堆复杂的if分支,如果把errstr放在堆上,处处要考虑是不是该把errstr free掉,得死多少脑细胞啊!

对于HOSTNAME是个运行时的信息,显然没法放到栈上,如果非要和errstr拼接起来的话,errstr必须在堆上,即要动态的malloc,由此可见,对于脚本语言来讲,一个非常简单的逻辑,在C中却如此的麻烦

 

解决办法:

  1. 为了既不需要free,又能拼接字符串,可以定义一个字符数组,如: errstr[255],然后,把需要的信息加进去;为了避免信息太多而不小心越界,可以使用snprintf函数,自动丢弃超长的部分

关于php-fpm reload的优雅实现

我们知道,nginx的reload是不会影响服务的,php-fpm也有reload 功能,而且确实不是restart,但是却是影响服务的,下面给出一种改进方式,是的php-fpm的reload不影响服务

修改源代码:

 

重写init脚本:

 

注意:

  1. 该功能要求linux内核版本较高,需要支持socket的SO_REUSEPORT 特性,我的3.10的内核已经支持了
  2. php-fpm 的pid文件是php-fpm进程来维护的,同时启动多个php-fpm不太方便,所以需要玩一些手段;虽然不指定pid文件参数也可以,但是我不知道如何才能方便地拿到php-fpm的pid信息,所以还是指定了pid参数

关于PHP中的长连接

长连接(也叫持久连接)是啥?

对于PHP来讲,如果是运行在web模式,每次请求完成,php会回收所有需要的申请的资源,但是也可以申请一些特殊的资源,php可以不回收,比如:长连接。

优点: 长连接省却了每次连接的麻烦,也有效减少了timewait的数量

长连接真的好吗?

  1. PHP中没有提供关闭(销毁)长连接的方法,当长连接出现意外时,将无所适从,更常见的是请求超时,都很不方便处理
  2. 同一个服务只能存在一个连接,如果想非阻塞地并发请求同一个服务(相同的目标ip、port),做不到!第二次的pfsockopen得到的是第一次pfsockopen的那个连接,php不会标识一个长连接是否在使用中

鉴于上面两点,感觉长连接真的好鸡肋

关于docker daemon占用大量内存的问题

问题现象:

问题环境:

dockerd 打开的文件数太多了, ~ 4w

类似如下:

和哪个容器相关还是和特定容器无关?

主要和docker-registry的那个容器相关(应该其他容器的个数少,不一定完全正常)

杀掉docker-registry这个容器,dockerd内存使用并未减少

但是相应的文件是不存在的了:

然后,只好重启dockerd试试了:

重启dockerd,问题解决(还好我们的dockerd重启不会影响容器的正常工作)

 

问题的非最终原因(但是也进了一步):

我们对所有的容器都是有监控的,基本手段是使用docker exec 在容器内执行命令,docker-registry这个容器比较特殊,没有我们要执行的命令,除了我们无法获取该容器信息外,没执行一次不存在的命令,dockerd就会在目录/run/docker/libcontainerd/ 下创建一个FIFO文件,并且始终打开着,不关闭(如果要执行的命令存在,则不会存在不关闭的情况)

详细测试结果如下:

对于一个返回值非零的命令,如果使用exec -i 选项,则不会残留打开的文件,如果不使用 exec -i 选项,会残留打开的文件(但是继续跟踪发现,该残留的时间不会太长)

对于一个不存在的命令,不管是否使用 -i 选项,总是会残留打开的文件;

解决办法:

为确保要执行的命令存在,可以使用如下方式:

该方式不但不会产生多余的未关闭文件描述符,而且可以执行一个命令序列,而直接docker exec -it cmd 只能执行一条命令

参考资料: https://github.com/docker/docker/issues/22095

 

shell 点滴 之 printf

将数组信息已多行格式输出

如何输出为:

 

strace 跟踪多个进程

strace是所有linux程序员都应该熟练掌握的工具,该命令可以strace -p $pid1 -p $pid2 跟踪一个或多个正在运行的进程,跟踪一个进程还好;当我们想strace httpd的所有进程时,我们可以pidof httpd拿到所有的pid,但是不能直接使用,需要在每个pid前面添加-p,用 while循环可以做到,如下:

倒是也不长,其实有更简单的办法:

如果我们已经将pid信息存到了$pids中了,那么可以:

注意:printf 参数中的 -p 最好不要顶着头写 (不过这个在使用xargs的时候不存在这个问题)