在开发的过程中,我们经常需要查看一下当前数据库的执行情况,比如如果我们select一个数据,很长时间都没有返回,我们在想,是不是什么sql语句把表锁着了,或者什么sql把资源占完了,那么我们怎么查这条sql呢?...
在开发的过程中,我们经常需要查看一下当前数据库的执行情况,比如如果我们select一个数据,很长时间都没有返回,我们在想,是不是什么sql语句把表锁着了,或者什么sql把资源占完了,那么我们怎么查这条sql呢?
办法肯定是有的:
mysql> show full processlist;
+-------+----------+----------------------+----------------+---------+------+-------+-----------------------+
| Id | User | Host | db | Command | Time | State | Info |
+-------+----------+----------------------+----------------+---------+------+-------+-----------------------+
| 25 | tuan_sql | 192.168.100.39:60161 | tao800 | Sleep | 18 | | NULL |
| 12196 | tuan_sql | 192.168.100.39:34410 | tao800 | Sleep | 89 | | NULL |
| 13067 | tuan_sql | 192.168.100.39:40964 | tao800 | Sleep | 123 | | NULL |
| 13158 | tuan_sql | 192.168.100.39:41496 | zhe_oem | Sleep | 3213 | | NULL |
| 13181 | tuan_sql | 192.168.100.39:41574 | tao800 | Sleep | 86 | | NULL |
| 13620 | tuan_sql | 192.168.100.39:44335 | tao800 | Sleep | 1 | | NULL |
| 13795 | tuan_sql | 192.168.100.39:45502 | tao800 | Sleep | 261 | | NULL |
| 14961 | tuan_sql | 192.168.100.39:55012 | tao800 | Sleep | 46 | | NULL |
| 15244 | tuan_sql | 192.168.100.39:57875 | tao800 | Sleep | 122 | | NULL |
| 15305 | tuan_sql | 192.168.100.39:58398 | zhe800_lottery | Sleep | 2705 | | NULL |
| 15306 | tuan_sql | 192.168.100.39:58399 | zhe800_lottery | Sleep | 2723 | | NULL |
| 15334 | tuan_sql | 192.168.100.39:58712 | tao800 | Sleep | 2151 | | NULL |
| 15422 | tuan_sql | 192.168.100.39:59571 | tao800 | Sleep | 27 | | NULL |
| 15434 | tuan_sql | 192.168.100.39:59633 | tao800 | Sleep | 18 | | NULL |
| 15521 | tuan_sql | 192.168.100.39:60435 | tao800 | Sleep | 1671 | | NULL |
| 15598 | tuan_sql | 192.168.100.39:32768 | tao800 | Sleep | 1151 | | NULL |
| 15661 | tuan_sql | 192.168.100.39:33489 | tao800 | Sleep | 643 | | NULL |
| 15677 | tuan_sql | 192.168.100.39:33663 | tao800 | Sleep | 264 | | NULL |
| 15682 | tuan_sql | 192.168.100.39:33716 | tao800 | Sleep | 264 | | NULL |
| 15690 | tuan_sql | 192.168.100.39:33791 | tao800 | Query | 0 | NULL | show full processlist |
| 15707 | tuan_sql | 192.168.100.39:34000 | zhe_oem | Sleep | 285 | | NULL |
| 15708 | tuan_sql | 192.168.100.39:34002 | zhe_oem | Sleep | 285 | | NULL |
+-------+----------+----------------------+----------------+---------+------+-------+-----------------------+
22 rows in set (0.00 sec)
我们详细介绍一下每个属性是什么意思吧

id

很容易就猜到是这个连接的id,值对应这个连接使用的线程id。

User

用户名

Host

连接的地址和端口,那么你可能会问,为什么我贴的例子的地址都是同一个host呢,很明显是用了代理,感兴趣的可以看看这篇文章http://www.phpv.net/html/1583.html,这里就不详细解释了。

db

此连接所连接使用的数据库名。

command

一般有俩值:sleep和query,从字面意思就可以看出,一个是处理睡眠状态,就是处于连接状态,但是没有正在执行任何sql语句,Query代表正在执行sql语句。

Time

连接的时间。

State

我个人觉得应该是当前线程的状态吧。

info

代表正在执行的sql,如果command为sleep时,它为空。
那如果我们遇到一个sql要执行很长时间,但是我们又不想让它继续执行了,怎么办呢?既然每个连接是在一个线程里维护,这个线程是否可以删除呢?我们尝试一下:
mysql> show full processlist; +-------+----------+----------------------+---------+---------+------+-------+-----------------------+ | Id | User | Host | db | Command | Time | State | Info | +-------+----------+----------------------+---------+---------+------+-------+-----------------------+ | 25 | tuan_sql | 192.168.100.39:60161 | tao800 | Sleep | 2 | | NULL | | 12196 | tuan_sql | 192.168.100.39:34410 | tao800 | Sleep | 43 | | NULL | | 13067 | tuan_sql | 192.168.100.39:40964 | tao800 | Sleep | 77 | | NULL | | 13181 | tuan_sql | 192.168.100.39:41574 | tao800 | Sleep | 69 | | NULL | | 13620 | tuan_sql | 192.168.100.39:44335 | tao800 | Sleep | 2 | | NULL | | 13795 | tuan_sql | 192.168.100.39:45502 | tao800 | Sleep | 215 | | NULL | | 14961 | tuan_sql | 192.168.100.39:55012 | tao800 | Sleep | 0 | | NULL | | 15244 | tuan_sql | 192.168.100.39:57875 | tao800 | Sleep | 76 | | NULL | | 15334 | tuan_sql | 192.168.100.39:58712 | tao800 | Sleep | 3305 | | NULL | | 15422 | tuan_sql | 192.168.100.39:59571 | tao800 | Sleep | 281 | | NULL | | 15434 | tuan_sql | 192.168.100.39:59633 | tao800 | Sleep | 272 | | NULL | | 15521 | tuan_sql | 192.168.100.39:60435 | tao800 | Sleep | 2825 | | NULL | | 15598 | tuan_sql | 192.168.100.39:32768 | tao800 | Sleep | 2305 | | NULL | | 15690 | tuan_sql | 192.168.100.39:33791 | tao800 | Query | 0 | NULL | show full processlist | | 15813 | tuan_sql | 192.168.100.39:35257 | zhe_oem | Sleep | 239 | | NULL | | 15814 | tuan_sql | 192.168.100.39:35258 | zhe_oem | Sleep | 239 | | NULL | +-------+----------+----------------------+---------+---------+------+-------+-----------------------+ 16 rows in set (0.00 sec)
我们杀掉15814这个线程:
mysql> kill 15814; Query OK, 0 rows affected (0.00 sec)
哈哈,还是可以的。
我们知道,不管是哪种数据库,或者是哪种数据库引擎,在对一条SQL语句进行执行的过程中都会做很多相关的优化。对于查询语句,最重要的优化方式就是使用索引,而执行计划,就是显示数据库引擎对于SQL语句的执行的...

五、SQL优化

5.1 如何定义以及优化SQL语句的性能问题?创建的索引有没有被使用到?或者说怎么才可以知道这条语句运行很慢的原因?

对于低性能的SQL语句的定位,最重要也是最有效的方法就是使用执行计划,Mysql提供了explain命令来查看语句的执行计划。我们知道,不管是哪种数据库,或者是哪种数据库引擎,在对一条SQL语句进行执行的过程中都会做很多相关的优化。对于查询语句,最重要的优化方式就是使用索引,而执行计划,就是显示数据库引擎对于SQL语句的执行的详细情况,其中包含了是否使用索引,使用什么索引,使用的索引的相关信息等。
执行计划包含的信息id有一组数字组成。表示一个查询中各个子查询的执行顺序;
id相同执行顺序由上至下。id不同,id值越大优先级越高,越先被执行。id为null时表示一个结果集,不需要使用它查询,常出现在包含union等查询语句中。
select_type每个子查询的查询类型,一些常见的查询类型。

idselect_typedescription
1SIMPLE不包含任何子查询或union等查询
2PRIMARY包含子查询最外层查询就显示为 PRIMARY
3SUBQUERY在select或where字句中包含的查询
4DERIVEDfrom字句中包含的查询
5UNION出现在union后的查询语句中
6UNION RESULT从UNION中获取结果集,例如上文的第三个例子

table查询的数据表,当从衍生表中查数据时会显示 x 表示对应的执行计划idpartitions表分区、表创建的时候可以指定通过那个列进行表分区。 举个例子:
createtable tmp( idintunsignednotnullAUTO_INCREMENT, namevarchar(255),PRIMARYKEY(id))engine=innodbpartitionbykey(id) partitions5;
type(非常重要,可以看到有没有走索引) 访问类型
ALL 扫描全表数据index 遍历索引range 索引范围查找index_subquery 在子查询中使用 refunique_subquery 在子查询中使用 eq_refref_or_null 对Null进行索引的优化的 reffulltext 使用全文索引ref 使用非唯一索引查找数据eq_ref 在join查询中使用PRIMARY KEYorUNIQUE NOT NULL索引关联。
possible_keys可能使用的索引,注意不一定会使用。查询涉及到的字段上若存在索引,则该索引将被列出来。当该列为 NULL时就要考虑当前的SQL是否需要优化了。
key显示MySQL在查询中实际使用的索引,若没有使用索引,显示为NULL。
TIPS:查询中若使用了覆盖索引(覆盖索引:索引的数据覆盖了需要查询的所有数据),则该索引仅出现在key列表中
key_length索引长度
ref表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
rows返回估算的结果集数目,并不是一个准确的值。
extra的信息非常丰富,常见的有:
Using index 使用覆盖索引Using where 使用了用where子句来过滤结果集Using filesort 使用文件排序,使用非索引列进行排序时出现,非常消耗性能,尽量优化。Using temporary 使用了临时表 sql优化的目标可以参考阿里开发手册
【推荐】SQL性能优化的目标:至少要达到 range 级别,要求是ref级别,如果可以是consts最好。
说明:
1) consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
2) ref 指的是使用普通的索引(normal index)。
3) range 对索引进行范围检索。
反例:explain表的结果,type=index,索引物理文件全扫描,速度非常慢,这个index级别比较range还低,与全表扫描是小巫见大巫。

5.2 SQL的生命周期

1.应用服务器与数据库服务器建立一个连接2.数据库进程拿到请求sql3.解析并生成执行计划,执行4.读取数据到内存并进行逻辑处理5.通过步骤一的连接,发送结果到客户端6.关掉连接,释放资源

5.3 大表数据查询,怎么优化?

1.优化schema,sql语句+索引;2.加缓存,memcached redis;3.主从复制,读写分离;4.垂直拆分,根据模块的耦合度,将一个大的系统拆分成小的系统,即分布式系统;5.水平拆分,针对数据量大的表,这一步最麻烦,最能考验技术水平,要选择一个合理的sharding key,为了有更好的查询效率,表结构也要改动,做一定的冗余,sql中尽量待sharding key,将数据定位到限定的表上去查,而不是扫描全部的表。

5.4大分页怎么处理?(缓存)

超大的分页一般从两个方向上来解决.
数据库层面,这也是我们主要集中关注的(虽然收效没那么大),类似于select * from table where age > 20 limit 1000000,10这种查询其实也是有可以优化的余地的. 这条语句需要load1000000数据然后基本上全部丢弃,只取10条当然比较慢. 当时我们可以修改为select * from table where id in (select id from table where age > 20 limit 1000000,10).这样虽然也load了一百万的数据,但是由于索引覆盖,要查询的所有字段都在索引中,所以速度会很快. 同时如果ID连续的好,我们还可以select * from table where id > 1000000 limit 10,效率也是不错的,优化的可能性有许多种,但是核心思想都一样,就是减少load的数据.从需求的角度减少这种请求…主要是不做类似的需求(直接跳转到几百万页之后的具体某一页.只允许逐页查看或者按照给定的路线走,这样可预测,可缓存)以及防止ID泄漏且连续被人恶意攻击.
解决超大分页,其实主要是靠缓存,可预测性的提前查到内容,缓存至redis等k-V数据库中,直接返回即可.
在阿里巴巴《Java开发手册》中,对超大分页的解决办法是类似于上面提到的第一种.
【推荐】利用延迟关联或者子查询优化超多分页场景。
说明:MySQL并不是跳过offset行,而是取offset+N行,然后返回放弃前offset行,返回N行,那当offset特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行SQL改写。
正例:先快速定位需要获取的id段,然后再关联:
SELECT a.* FROM 表1 a, (select id from 表1 where 条件 LIMIT 100000,20 ) b where a.id=b.id

5.5 Mysql 分页 limit

LIMIT子句可以被用于强制SELECT语句返回执行的记录数。LIMIT接受一个或两个数字参数。参数必须是一个整数常量。如果给定两个参数,第一个参数指定第一个返回记录行的偏移量,第二个参数执行返回记录行的最大数目。初始记录行的偏移量是0(而不是1)
mysql>SELECT*FROMtableLIMIT5,10;// 检索记录行 6-15
为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:
mysql>SELECT*FROMtableLIMIT95,-1;// 检索记录行 96-last.
如果只给定一个参数,它表示返回最大的记录行数目:
mysql>SELECT*FROMtableLIMIT5;//检索前 5 个记录行
换句话说,LIMIT n 等价于 LIMIT 0,n。

5.6 慢查询日志

用于记录执行时间超过某个临界值的SQL日志,用于快速定位慢查询,为我们的优化做参考。
开启慢查询日志
配置项:slow_query_log
可以使用show variables like ‘slov_query_log’查看是否开启,如果状态值为OFF,可以使用set GLOBAL slow_query_log = on来开启,它会在datadir下产生一个xxx-slow.log的文件。
设置临界时间
配置项:long_query_time
查看:show VARIABLES like 'long_query_time',单位秒
设置:set long_query_time=0.5
实操时应该从长时间设置到短的时间,即将最慢的SQL优化掉
查看日志,一旦SQL超过了我们设置的临界时间就会被记录到xxx-slow.log中

5.7 关心过业务系统里面的sql耗时吗?统计过慢查询吗?对慢查询都怎么优化过?

慢查询的优化首先要搞明白慢的原因是什么? 是查询条件没有命中索引?是load了不需要的数据列?还是数据量太大?
所以优化也是针对这三个方向来的,
首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写。分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表,或者考虑缓存。

5.8 为什么要尽量设定一个主键?

主键是数据库确保数据行在整张表唯一性的保障,即使业务上本张表没有主键,也建议添加一个自增长的ID列作为主键。设定了主键之后,在后续的删改查的时候可能更加快速以及确保操作数据范围安全。

5.9 主键使用自增ID还UUID

推荐使用自增ID,不要使用UUID。
因为在InnoDB存储引擎中,主键索引是作为聚簇索引存在的,也就是说,主键索引的B+树叶子节点上存储了主键索引以及全部的数据(按照顺序),如果主键索引是自增ID,那么只需要不断向后排列即可,如果是UUID,由于到来的ID与原来的大小不确定,会造成非常多的数据插入,数据移动,然后导致产生很多的内存碎片,进而造成插入性能的下降。
总之,在数据量大一些的情况下,用自增主键性能会好一些。
关于主键是聚簇索引,如果没有主键,InnoDB会选择一个唯一键来作为聚簇索引,如果没有唯一键,会生成一个隐式的主键。

5.10 字段为什么要求定义为not null?

null值会占用更多的字节,且会在程序中造成很多与预期不符的情况。

5.11 如果要存储用户的密码散列,应该使用什么字段进行存储?

密码散列,盐,用户身份证号等固定长度的字符串应该使用char而不是varchar来存储,这样可以节省空间且提高检索效率。

5.12 优化查询过程中的数据访问

访问数据太多导致查询性能下降:
确定应用程序是否在检索大量超过需要的数据,可能是太多行或列;确定Mysql服务器是否在分析大量不必要的数据行;
查询不需要的数据:
使用limit解决
多表关联返回全部列:
指定列名
总是返回全部列:
避免使用select *
重复查询相同的数据:
缓存数据,下次直接读取缓存
是否在扫描额外的记录:
使用explain进行分析,如果发现查询需要扫描大量的数据,但只返回少数的行,可以通过以下技巧去优化:使用索引覆盖扫描,把所有的列放到索引中,这样存储引擎不需要回表获取对应行就可以返回结果;改变数据库和表的结构,修改数据表范式重写SQL语句,让优化器可以更优的方式执行查询。

5.13 优化

5.13.1 优化长难的查询语句

使用一个复杂查询还是多个简单查询
使用尽可能小的查询是好的,有时将一个大的查询分解为多个小的查询是很有必要的;切分查询,将一个大的查询划分为多个小的相同查询;一次性删除1000万的数据要比删除1万,暂停一会的方案更加损耗服务器开销;执行单个查询可以减少锁的竞争,查询效率会有大幅提升;

5.13.2 优化关联查询

确定ON或者USING子句中是否有索引。确保GROUP BY和ORDER BY只有一个表中的列,这样MySQL才有可能使用索引。

5.13.3 优化子查询

用关联查询替代优化GROUP BY和DISTINCT这两种查询据可以使用索引来优化,是最有效的优化方法关联查询中,使用标识列分组的效率更高如果不需要ORDER BY,进行GROUP BY时加ORDER BY NULL,MySQL不会再进行文件排序。WITH ROLLUP超级聚合,可以挪到应用程序处理

5.13.4 优化LIMIT分页

LIMIT偏移量大的时候,查询效率较低可以记录上次查询的最大ID,下次查询时直接根据该ID来查询

5.13.5 优化UNION查询

UNION ALL的效率高于UNION

5.13.6 优化WHERE子句