2.2.3.7.27 Waiting for net, reading from net, writing to net
如大量出现,迅速检查数据库到前端的网络连接状态和流量
2.2.3.7 Info
一般记录的是线程执行的语句(sql)。默认只显示前100个字符
也就是你看到的语句可能是截断了的,要看全部信息,需要使用 show full processlist
2.2.3.8 相关案例
Mysql里面执行 show processlist 出现很多 Sleep 状态的线程 由于程序设计的Bug,导致目前这个项目使用的数据库中有很多Sleep状态的线程。找了很多解决办法,还没发现最终有效的解决方案,临时解决方案如下:
#编写shell文件,如killSleepProcess.sh
# 每隔120 秒 执行一次 done
[plain] while : do n=`mysqladmin -uroot -p*** processlist | grep -i sleep | wc -l` date=`date +%Y%m%d/[%H:%M:%S]` echo $n if [ "$n" -gt 10 ] then for i in `mysqladmin -uroot -p*** processlist | grep -i sleep | awk '{print $2}'` do mysqladmin -uroot -p*** kill $i done echo "sleep is too many i killed it" >> /root/tmp/sleep.log echo "$date : $n" >> /root/tmp/sleep.log fi sleep 120
按客户端 IP 分组,看哪个客户端的链接数最多
select client_ip,count(client_ip) as client_num from (select substring_index(host,':' ,1) as client_ip from information_schema.processlist ) as connect_info group by client_ip order by client_num desc;
查看正在执行的线程,并按 Time 倒排序,看看有没有执行时间特别长的线程
select * from information_schema.processlist where Command != 'Sleep' order by Time desc;
找出所有执行时间超过 5 分钟的线程,拼凑出 kill 语句,方便后面查杀
select concat('kill ', id, ';') from information_schema.processlist where Command != 'Sleep' and Time > 300 order by Time desc;
2.3 常用的sql优化
2.3.1 优化分页查询
通常我们是使用 LIMIT M,N + 合适的 order by 来实现分页查询
这种分页在没有索引的情况下,需要做大量的文件排序操作,性能很差
在有索引的情况下,刚开始查询效率比较理想,但越往后,分页查询性能就越差(偏移量 M 在分页越靠后的时候,值就越大,数据库检索的数据也就越多)
# limit m,n -- 大概0.018s
select * from `demo`.`order` order by order_no limit 10000, 20;
# 子查询,遍历索引的范围跟limit m,n差不多,主查询还扫描了更多的行
# 但执行时间却减少了 -- -- 大概0.004s
# 这是因为查询返回的数据只要20行了,而以前的是要10020行
select * from ``order` where id >= (select id from `order` order by order_no limit 10000, 1) limit 20;
InnoDB 在默认创建主键时会创建主键索引,而主键索引属于聚簇索引,即在存储数据时,索引是基于 B + 树构成的,具体的行数据则存储在叶子节点。
MyISAM 默认创建的主键索引、二级索引以及 InnoDB 的二级索引都属于非聚簇索引,即在存储数据时,索引是基于 B + 树构成的,而叶子节点存储的是主键值
## 一张order表 存在 order_no、status 两列组成的组合索引
## 此时需要根据订单号查询一张订单表的 status
select * from order where order_no='xxx’; //先查组合索引,通过组合索引获取到主键Id,再通过主键id去主键索引中获取对应行所有列的值
select order_no,status from order where order_no='xxx’; //只会查询组合索引,通过组合索引获取到对应的 order_no 和 status 的值
2.3.4 where 子句优化
2.3.4.1 在 where 及 order by 涉及的列上建立索引
2.3.4.2 尽量避免使用 != 和 <> 、not
此时mysql 存储引擎会放弃索引而进行全表扫描
2.3.4.3 尽量避免在 where 子句中对字段进行 null 值判断
此时mysql 存储引擎会放弃索引而进行全表扫描
可以在字段上设置默认值,确保表中列没有null值
2.3.4.4 尽量避免在 where 子句中使用 or 来连接条件
此时mysql 存储引擎会放弃索引而进行全表扫描
用 union/union all 来替代
select id from t where num=10 or num=20
可替换为:
select id from t where num=10
union all
select id from t where num=20
2.3.4.5 尽量避免使用 %前缀、%前缀% 模糊查询
这种查询会导致索引失效而进行全表扫描,但可以使用 LIKE “name%”
可以使用全文检索解决
//对要查询的字段,建立全文索引
ALTER TABLE `table_name` ADD FULLTEXT INDEX `idx_column` (`column`);
//使用全文索引sql示例
select id,name from `table_name` where match(`column`) against("具体值" in boolean mode);
2.3.4.6 尽量避免使用 in 或者 not in 结合子查询语句,应该是具体值(但也不宜过多),否则会导致索引失效
对于连续值,能用 between 就不要用 in
可用 exsits 代替 in
select * from 表A where id in (select id from 表B)
上面SQL语句相当于
select * from 表A where exists(select * from 表B where 表B.id=表A.id)
区分in和exists主要是造成了驱动顺序的改变(这是性能变化的关键)
exists,那么以外层表为驱动表,先被访问;适合外表小而内表大的情况
IN,那么先执行子查询,以内表为驱动表;适合外表大而内表小的情况
关于 not in 和 not exists,推荐使用 not exists,不仅仅是效率问题,not in可能存在逻辑问题
如何高效的写出一个替代not exists的SQL语句?
原SQL语句:
select colname … from A表 where a.id not in (select b.id from B表)
高效的SQL语句:
select colname … from A表 Left join B表 on a.id = b.id where b.id is null
select id from t where num=@num
可以改为强制查询使用索引:
select id from t with(index(索引名)) where num=@num
2.3.4.8 尽量避免在 where 子句 对字段进行表达式操作
会导致索引失效
select id from t where num/2=100
应改为:
select id from t where num=100*2
2.3.4.9 尽量避免在 where子句 中对字段进行函数操作
会导致索引失效
select id from t where substring(name,1,3)='abc'--name以abc开头的id
select id from t where datediff(day,createdate,'2005-11-30')=0--'2005-11-30'生成的id
应改为:
select id from t where name like 'abc%'
select id from t where createdate>='2005-11-30' and createdate<'2005-12-1'
union 和 union all的差异主要是前者需要将结果集合并后再进行唯一性过滤操作,这就会涉及到排序,增加大量的CPU运算,加大资源消耗及延迟
2.3.6 不使用ORDER BY RAND() – 随机排序
RAND() 函数效率极低,因为这样会导致数据列被多次扫描
select id from `dynamic` order by rand() limit 1000;
上面的SQL语句,可优化为:
select id from `dynamic` t1 join (select rand() * (select max(id) from `dynamic`) as nid) t2 on t1.id > t2.nidlimit 1000;
# t1 的 FilterID 字段设置了索引,t1和t2的 CommonID 也建立了索引
select t1.* from Table1 t1 INNER JOIN Table2 t2 on t1.CommonID = t2.CommonID
where t1.FilterID = 1
// 此时由于Mysql自动选择的驱动表为 Table2 即 Table2 驱动 Table1,导致FilterID索引失效,查询效率降低
// 这是可以用 STRAIGHT_JOIN 来替换,强制是驱动表为 Table1
select t1.* from Table1 t1 STRAIGHT_JOIN Table2 t2 on t1.CommonID = t2.CommonID
where t1.FilterID = 1
2.4 慢查询记录配置
2.4.1 开启慢查询记录配置
set global slow_query_log='ON'; //开启慢SQL日志
set global slow_query_log_file='/var/lib/mysql/test-slow.log';//记录日志地址
set global long_query_time=1;//最大执行时间
2.4.2 慢 SQL 的功能查询
Show variables like 'slow_query%'; //是否开启了记录慢 SQL 的功能
Show variables like 'long_query_time'; //最大的执行时间
在 Select、Update、Delete,除了基于唯一索引的查询之外,其他索引查询都会获取 gap lock 或者 next-key lock
4.6.1.1.1 record lock
专门对索引项加锁
4.6.1.1.2 gap lock
对索引项之间的间隙加锁
4.6.1.1.3 next-key lock
是前面两种的组合,对索引项以其之间的间隙加锁
4.6.1.2 行锁的兼容性
4.6.1.3 具体案例
利用数据库来校验幂等性
首先将Mysql的事务提交改为手动提交,默认为自动提交
// 查看自动提交事务是否开启
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.01 sec)
// 将自动提交设置为0,即改为手动提交
mysql> set autocommit = 0;
Query OK, 0 rows affected (0.00 sec)
// 表结构
CREATE TABLE `order_record` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_no` int(11) DEFAULT NULL,
`status` int(4) DEFAULT NULL,
`create_date` datetime(0) DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_order_status`(`order_no`,`status`) USING BTREE
) ENGINE = InnoDB
图中的Select 操作,由于 order_no 列为非唯一索引,而且Mysql默认事务隔离级别为RR(可重复读),所以Select操作的加锁类型为 gap lock,gap范围为(4,+∞)
由行锁的兼容性可知,Select 获取的 gap lock 不会导致阻塞,但当执行插入Sql时,会在插入间隙上再次获取 插入意向锁(insert intension lock),其实也是一种 gap lock,但它与gap lock是冲突的,所以当其它事务持有该间隙的 gap lock 时,需要等待其它事务释放 gap lock 之后,才能获取到插入意向锁
以上事务 A 和事务 B 都持有间隙 (4,+∞)的 gap lock,而接下来的插入操作为了获取到插入意向锁,都在等待对方事务的 gap 锁释放,于是就造成了循环等待,导致死锁
优化查询。在 where 子句中包含分区条件时,可以只扫描必要的一个或者多个分区来提高查询效率;同时在涉及 sum() 和 count() 这类聚合函数的查询时,可以容易的在每个分区上并行处理,最终只需要汇总所有分区得到的结果
对于已经过期或者不需要保存的数据,可以通过删除与这些数据有关的分区来快速删除数据
跨多个磁盘来分散数据查询,以获得更大的查询吞吐量
5.1.3 分区类型
-- 查看mysql版本
select version();
-- 查看分区插件是否激活 partition active
show plugins;
-- 对于低版本的MySQL,如果InnoDB引擎要想分区成功,需要在my.conf中设置innodb_file_per_table=1 设置成独立表空间
-- 独立表空间:每张表都有对应的.ibd文件
innodb_file_per_table=1
5.1.3.1 Range 分区
最为常用,基于属于一个给定连续区间的列值,把多行分配给分区。
最常见的是基于时间字段. 基于分区的列最好是整型,如果日期型的可以使用函数转换为整型
5.1.3.1.1 语法
create table <table> (
// 字段
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1
partition by range (分区字段) (
partition <分区名称> values less than (Value),
partition <分区名称> values less than (Value),
...
partition <分区名称> values less than maxvalue
);
-- 也可以在创建表之后进行分区
alter table <table> partition by RANGE(id) (
PARTITION p0 VALUES LESS THAN (1000000),
PARTITION p1 VALUES LESS THAN (2000000),
PARTITION p2 VALUES LESS THAN (3000000),
PARTITION p3 VALUES LESS THAN (4000000),
PARTITION p4 VALUES LESS THAN MAXVALUE
);
Value: 表示要小于某个具体的值,如 less than(10),那么分区字段小于10的都会被分到这个分区
maxvalue:表示一个最大的值
5.1.3.1.2 range columns
columns分区支持一个或者多个字段作为分区键,不支持表达式作为分区键
create talbe rc3 (
a int,
b int
)
partition by range columns(a, b) (
partition p01 values less than (0, 10),
partition p02 values less than (10, 10),
partition p03 values less than (10, 20),
partition p04 values less than (10, 35),
partition p05 values less than (10, maxvalue),
partition p06 values less than (maxvalue, maxvalue),
);
insert into rc3(a, b) values(1, 10);
select (1, 10) < (10, 10) from dual;
-- 根据结果存放到p02分区上了
select
partition_name,
partition_expression,
partition_description,
table_rows
from information_schema.partitions
where table_schema = schema() and table_name = 'rc3';
range columns分区键的比较(元组的比较)其实就是多列排序,先根据a字段排序再根据b字段排
KEY分区和HASH分区的算法不一样,PARTITION BY HASH (expr),MOD取值的对象是expr返回的值,而PARTITION BY KEY (column_list),基于的是列的MD5值
5.1.3.4.1 语法
create table <table> (
// 字段
) ENGINE=数据库引擎 DEFAULT CHARSET=utf8 AUTO_INCREMENT=1
PARTITION BY key(expr)
//PARTITION BY key() -- 不指定expr默认首选主键作为分区键,在没有主键的情况下会选择非空唯一键作为分区键
//partition by linear key(expr)
PARTITIONS <num>;
5.1.3.5 子分区
是分区表中对每个分区的再次分割,又被称为复合分区
支持对range和list进行子分区,子分区即可以使用hash分区也可以使用key分区
适用于保存非常大量的数据记录
5.1.3.5.1 语法案例
-- 根据年进行分区
-- 再根据天数分区
-- 3个range分区(p0,p1,p2)又被进一步分成2个子分区,实际上整个分区被分成了 3 x 2 = 6个分区
create table ts (
id int,
purchased date
)
partition by range(year(purchased))
subpartition by hash(to_days(purchased)) subpartitions 2
(
partition p0 values less than (1990),
partition p0 values less than (2000),
partition p0 values less than maxvalue
);
5.1.4 管理分区
5.1.4.1 添加分区
-- range添加新分区
alter table <table> add partition(partition p4 values less than MAXVALUE);
-- list添加新分区
alter table <table> add partition(partition p4 values in (25,26,28));
5.1.4.2 拆分分区
alter table access_log reorganize partition p4 into(
-> partition s0 values less than(to_days('20190104')),
-> partition s1 values less than(to_days('20190105'))
-> );
5.1.4.3 删除分区
-- 删除list或者range分区(同时删除分区对应的数据)
alter table <table> drop partition <分区名称>;
5.1.4.4 合并分区
alter table access_log reorganize partition s0,s1 into (
partition p4 values less than (to_days('20190105'))
);
-- range合并分区
ALTER TABLE user REORGANIZE PARTITION p0,p1,p2,p3,p4 INTO (PARTITION p0 VALUES LESS THAN MAXVALUE);
-- list合并分区
ALTER TABLE <table> REORGANIZE PARTITION p0,p1,p2,p3,p4 INTO (PARTITION p0 VALUES in (1,2,3,4,5));
//这个参数最合适的大小和应用程序查询结果的平均大小直接相关,可以通过以下公式计算所得
(query_cache_size - Qcache_free_memory)/ Qcache_queries_in_cache
// Qcache_free_memory 和 Qcache_queries_in_cache 的值可以通过以下命令查询:
show status like 'Qcache%'
( innodb_read_io_threads + innodb_write_io_threads ) = innodb_buffe_pool_instances
我们可以通过以下查询来确定读写比率:
SHOW GLOBAL STATUS LIKE 'Com_select';//读取数量
SHOW GLOBAL STATUS WHERE Variable_name IN ('Com_insert', 'Com_update', 'Com_replace', 'Com_delete');//写入数量