MYSQL 最左前缀法则

什么是最左前缀法则呢?最左前缀法则是指复合索引当中要遵守的法则,先说结论:指查询从索引的最左列开始,并且不跳过索引中的列。也就是说查询的条件当中要包含索引最左侧的列,并且不能跳过索引当中的列。

如何理解呢?实际来个例子吧

create index idx_seller_name_sta_addr on tb_seller(name, status, address);
explain select * from tb_seller where name = '小米科技' and status = '1' and address ='北京市' //针对上面的索引,这样来查肯定是没有问题的

接下来再做一个测试 :

explain select * from tb_seller where name = '小米科技' //这样会走索引吗?结论是:也会

接下来进一步做测试:

explain select * from tb_seller where name = '小米科技' and status = '1'  //结论:也会走索引,可以看key_len区分索引的不同

接下来再做一下测试:

explain select * from tb_seller where status = '1' and address = '北京市' //这样走索引吗?不走,因为没从索引最左边的列开始

再做一轮测试:

explain select * from tb_seller where address = '北京市' //这样走索引吗?不走,因为也没从索引最左边的列开始

再做一轮测试:name顺序发生变化

explain select * from tb_seller where status = '1' and address = '北京市' and name = '小米科技' //走索引,顺序并不影响,只看查询条件有没有包含最左列

还有一种特殊情况:

explain select * from tb_seller where name = '小米科技' and address = '北京市' //走索引,只用到了name字段的索引,没用到address字段的索引

可以把这个想象成爬楼梯,name 第一层,status 第二层,address 第三层,不能跳过第一层,直接爬后面的。也不能爬完第一层后直接爬第三层。

git 回退版本

Git必须知道当前版本是哪个版本,在Git中,用HEAD表示当前版本,,上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100。

现在,我们要把当前版本A ,回退到上一个版本“B”,就可以使用git reset命令:

$ git reset –hard HEAD^

HEAD is now at ea34578 B

也可以查询版本 :git log –pretty=oneline

显示出所有提交版本记录,选择 id

执行 git rest –hard id即可。

nginx lua 模块编译

背景:https://www.clarkhu.net/?p=10121 (需要使用request_id定位业务问题)

参考文章:

https://github.com/openresty/lua-nginx-module#nginx-compatibility

需要的安装包有:

nginx-1.19.3.tar.gz
lua-nginx-module-0.10.14.tar.gz
ngx_devel_kit-0.3.1.tar.gz
openssl-1.0.2j.tar.gz
pcre-8.36.tar.gz
luajit2-2.1-20210510.tar.gz

make PREFIX=/usr/local/luajit
make install PREFIX=/usr/local/luajit
export LUAJIT_LIB=/usr/local/luajit/lib
export LUAJIT_INC=/usr/local/luajit/include/luajit-2.1


tar zxf lua-nginx-module-0.10.14.tar.gz
tar zxf ngx_devel_kit-0.3.1.tar.gz
tar xvzf nginx-1.19.3.tar.gz

./configure --prefix=/usr/local/nginx-1.19.3 --with-http_ssl_module --with-openssl=/usr/local/src/openssl-1.0.2n --with-pcre=/usr/local/src/pcre-8.36 --with-http_secure_link_module --with-ld-opt=-Wl,-rpath,/usr/local/luajit/lib --add-module=/usr/local/src/ngx_devel_kit-0.3.1 --add-module=/usr/local/src/lua-nginx-module-0.10.14

make -j2
make install

这里主要的是nginx, lua-nginx ngx_devel luajit 这几个包的兼容性问题需要自己去查和测试。之前遇到了resty.core not found ,上网搜了下,主要就是包的兼容性问题导致的

df -h 卡住不动问题解决

早上测试环境打不开,登录看测试环境负载较高,页面提示貌似是磁盘满了。于是df -h想查一下磁盘情况,结果发现 df -h 卡住不动

于是mount -l 看了一下该机器上的目录情况,发现有一个

ip:port on /data/xxx type fuse.mfs (rw,nosuid,nodev,relatime,user_id=0,group_id=0,allow_other)

telnet访问了一下该ip和port, 发现策略不通,机器已经被回收了,所以现在df -h 打不开,
于是用umount -l /data/xxx 解除挂载

最后再次df -h,发现ok了。

mysql计算db size

SELECT sum(TABLE_ROWS) as rows, sum(DATA_LENGTH)/1024/1024 as data_size, sum(INDEX_LENGTH)/1024/1024 as index_size FROM  information_schema.`TABLES` WHERE TABLE_SCHEMA='库名'

db_size = data_size + index_size

//更多

//第一种情况:查询所有数据库的总大小,方法如下:
mysql> use information_schema;
mysql> select concat(round(sum(DATA_LENGTH/1024/1024),2),'MB') as data from TABLES;
+-----------+
| data      |
+-----------+
| 3052.76MB |
+-----------+
1 row in set (0.02 sec)

//统计一下所有库数据量 
每张表数据量=AVG_ROW_LENGTH*TABLE_ROWS+INDEX_LENGTH
SELECT
SUM(AVG_ROW_LENGTH*TABLE_ROWS+INDEX_LENGTH)/1024/1024 AS total_mb
FROM information_schema.TABLES 
统计每个库大小:
SELECT
table_schema,SUM(AVG_ROW_LENGTH*TABLE_ROWS+INDEX_LENGTH)/1024/1024 AS total_mb
FROM information_schema.TABLES group by table_schema;  


//第二种情况:查看指定数据库的大小,比如说:数据库test,方法如下:
mysql> use information_schema;
mysql> select concat(round(sum(DATA_LENGTH/1024/1024),2),'MB') as data from TABLES where table_schema='test';
+----------+
| data     |
+----------+
| 142.84MB |
+----------+
1 row in set (0.00 sec)

//1.查看所有数据库各容量大小
select
table_schema as '数据库',
sum(table_rows) as '记录数',
sum(truncate(data_length/1024/1024, 2)) as '数据容量(MB)',
sum(truncate(index_length/1024/1024, 2)) as '索引容量(MB)'
from information_schema.tables
group by table_schema
order by sum(data_length) desc, sum(index_length) desc;

//2.查看所有数据库各表容量大小
select
table_schema as '数据库',
table_name as '表名',
table_rows as '记录数',
truncate(data_length/1024/1024, 2) as '数据容量(MB)',
truncate(index_length/1024/1024, 2) as '索引容量(MB)'
from information_schema.tables
order by data_length desc, index_length desc;

//3.查看指定数据库容量大小
例:查看mysql库容量大小
select
table_schema as '数据库',
sum(table_rows) as '记录数',
sum(truncate(data_length/1024/1024, 2)) as '数据容量(MB)',
sum(truncate(index_length/1024/1024, 2)) as '索引容量(MB)'
from information_schema.tables
where table_schema='mysql'; 

//4.查看指定数据库各表容量大小
//例:查看mysql库各表容量大小
select
table_schema as '数据库',
table_name as '表名',
table_rows as '记录数',
truncate(data_length/1024/1024, 2) as '数据容量(MB)',
truncate(index_length/1024/1024, 2) as '索引容量(MB)'
from information_schema.tables
where table_schema='mysql'
order by data_length desc, index_length desc;

Git将一个分支完全覆盖另一个分支的操作方法

假设每个人有个开发分支,想隔一段时间就把自己的开发分支上的代码保持和测试分支一直,则需要如下操作:

1.我想将test分支上的代码完全覆盖dev分支,首先切换到dev分支
git checkout dev

2.然后直接设置代码给远程的test分支上的代码
git reset –hard origin/test

3.执行上面的命令后dev分支上的代码就完全被test分支上的代码覆盖了,注意只是本地分支,这时候还需要将本地分支强行推到远程分支。
git push -f 本地分支:远程分支

重试机制的代码套路

<?php
$NUM_OF_ATTEMPTS = 5;
$attempts = 0;

do {

    try
    {
        executeCode();
    } catch (Exception $e) {
        $attempts++;
        sleep(1);
        continue;
    }

    break;

} while($attempts < $NUM_OF_ATTEMPTS);

Nginx 使用requestid 对应用程序日志及性能追踪

问题描述:业务侧经常会遇到一个问题,可以拿到某个慢查询的SQL(比如mysql slowlog, 或是使用的云产品打开实例页面查看对应的慢日志),但是确很难找到对应的业务代码在哪里?(除非SQL本身具有特殊识别性,或是对业务系统非常之熟悉外,如果很雷同的,找起来确实很痛苦,亲测是这样)

针对上面出现的这个问题,nginx request_id 可以完美解决

nginx 从1.11 之后支持生成request_id

部署方案:

1.nginx 接入层关键配置

map $http_x_log_request_id $log_request_id {
    default $http_x_log_request_id;
    -       $request_id;
    ""      $request_id;
}

location ~ .*\.(php|php5)?$ {
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_index   index.php;
    fastcgi_param   SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param   LOG_REQUEST_ID $log_request_id;
    include         fastcgi_params;
    fastcgi_intercept_errors on;
    error_page      500 502 503 504  /50x_php.html;
}

2.业务层WEB框架入口代码:设置全局变量

$GLOBALS['LOG_REQUEST_ID'] = !empty($_SERVER['LOG_REQUEST_ID']) ? $_SERVER['LOG_REQUEST_ID'] : $_SERVER['REQUEST_TIME_FLOAT'];

3.业务层JOB框架入口代码:设置全局变量,由于job不经过nginx层,所以request_id自己生成

<?php
$job_request_id = md5("{$script_name}." . microtime(true));
$GLOBALS['LOG_JOB_REQUEST_ID'] = $job_request_id;
?>

4.业务日志收集到文件

<?php
....
$log['controller'] = controller值
$log['action'] = action值
$log['url'] = 当前请求的url, 可以用$_SERVER['REQUEST_URI']获取;
$log['refer'] = //refer, 可以从$_SERVER获取;
$log['user_agent'] = user_agent值,可以从$_SERVER获取;
$log['ip'] = ip获取函数
$log['http_status'] = http状态码,可以从$_SERVER获取;
$log['log_request_id'] = isset($GLOBAL['LOG_REQUEST_ID']) ? isset($GLOBAL['LOG_REQUEST_ID']) : '';
write_log(json_encode($log));
....
?>

5.将第三步写的log导入到es中,便于查询log_request_id对应的请求来源

6.框架SQL执行处代码修改:

$comment_token = '';
if(defined('IS_JOB') && IS_JOB) {
	if(isset($GLOBALS['LOG_JOB_REQUEST_ID']) && !empty($GLOBALS['LOG_JOB_REQUEST_ID'])) {
		$comment_token = '/*job_' . $GLOBALS['LOG_JOB_REQUEST_ID'] . '*/';
	}
} else {
	if(isset($GLOBALS['LOG_REQUEST_ID']) && !empty($GLOBALS['LOG_REQUEST_ID'])) {
		$comment_token = '/*web_' . $GLOBALS['LOG_REQUEST_ID'] . '*/';
	}
}
$sql = $sql . $comment_token;

$this->_result = $this->_execute($sql);

这里在执行的SQL语句后面接上/*request_id*/,并不影响SQL本身的执行,与此同时还能知道SQL的来源,下图为慢日志查询中显示的SQL语句,带上了request_id, 这样是不是就可以得容易知SQL语句来自业务哪了呢?

根据上述步骤1,2,3,4,5,就可以根据慢日志查询的SQL, 反查到对应的业务请求来源了,是不是很帅?

mysql processlist聚合分析

昨晚db告警,从慢日志和异常日志行为分析,表现都不太明显(并不像以往api请求变更历史那样有明显的IP和action或是用户信息,比较分散),虽然log初步分析是api流量有点大,但是最后都由于自己不够坚定而放弃了怀疑,说白了,还是自己技术不够到位,事后分析了下,慢日志和异常行为日志这两者如果不明显,但是db进程数(出现很多sleep进程)一直告警,是否可以通过processlist host出现的次数,来判断sql来源呢?如果host指向api,是不是就可以坐实了,那如何统计processlist host出现的次数?

show full processlist;并不支持类似like的过滤。于是想通过这个命令筛选得到某些服务器IP的连接不可取。但并不是没有好的办法,因为show full processlist;命令本身就是查询表PROCESSLIST里的内容,表PROCESSLIST在数据库information_schema中,我们在执行show processlist时实际在mysql中就是执行查询information_schema数据库中的processlist表查询。这就好办了,因为字段HOST中的值多是ip:port,这种IP加端口结构(当然我上面有的是localhost,这个不用考虑,大网站都会是多个WEB连接MYSQL,基本都是IP端口),所以上面使用字符串以逗号分隔,从而截取IP地址出来。所以如果想根据HOST中的连接IP地址来进行分组统计,可以使用SQL:

select server,count(server) as servernum from (select substring_index(host,':' ,1) as server from processlist ) as rsa group by server order by servernum desc;

substring_index(字段名, ‘:’, 1)的作用:截取第1个 ‘:’ 之前的所有字符。非常有用。

其实除此之外,还可以通过其他手段坐实问题
1.API容器当中php slow log (/xxxx/php/slow.log)
2.API容器当中php error.log(/xxxx/php/error.log)

memcache安全启动

今天遇到一个很老的服务,用了memcached做缓存,最近HW行动弄的人胆战心惊,memcached 如何在对外服务的时候,保证安全呢?

1.安全启动

/usr/local/memcached/bin/memcached -u 普通用户 -l 内网IP地址 -p 指定特殊端口(不以11211默认端口启动)  #启动服务

2.设置防火墙规则 (11211为memcached的默认端口)

iptables -A INPUT -p tcp -s 来源IP地址 –dport memcached的端口号 -j ACCEPT

iptables -A INPUT -p tcp –dport memcached的端口号 -j DROP 限制tcp

iptables -A INPUT -p udp –dport memcached的端口号 -j DROP 限制udp