时间:2021-07-01 10:21:17 帮助过:1人阅读
mysql> show full processlist; +----+------+-----------+--------+---------+------+-------+------------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+------+-----------+--------+---------+------+-------+------------------------+ | 39 | root | localhost | sakila | Sleep | 4 | | NULL | | 40 | root | localhost | sakila | Query | 0 | NULL | show full processlist | +----+------+-----------+--------+---------+------+-------+------------------------+ 2 rows in set (0.00 sec)
查询优化器
mysql> select count(*) from film_actor; +----------+ | count(*) | +----------+ | 5462 | +----------+ 1 row in set (0.00 sec) mysql> show status like ‘last_query_cost‘; +-----------------+-------------+ | Variable_name | Value | +-----------------+-------------+ | Last_query_cost | 1040.599000 | +-----------------+-------------+
这个结果表示mysql优化器认为大概需要做1040个数据页的随机查找才能完成上面的查询。这是根据一系列的统计信息计算得来的:每个表或者索引的页面个数,索引的基数(索引中不同值的数量),索引和数据行的长度,索引分布情况。 优化器在评估成本的时候并不考虑任何层面的缓存,它假设读取任何数据都需要一次磁盘I/O。 mysql优化器选错执行计划的原因:
mysql> select * from film where film_id in ( select film_id from film_actor where actor_id =1) \G;
我们一般认为,mysql会首先将子查询的actor_id=1的所有film_id都找到,然后再去做外部查询,如
select * from film where film_id in (1,23,25,106,140);
然而,mysql不是这样做的。 mysql会将相关的外层表压到子查询中,它认为这样可以更高效率地查找数据行。 当然我们可以使用连接替代子查询重写这个SQL,来优化;
mysql> explain select * from film f inner join film_actor fa where f.film_id=fa.film_id and actor_id =1; +----+-------------+-------+--------+------------------------+---------+---------+-------------------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+--------+------------------------+---------+---------+-------------------+------+-------+ | 1 | SIMPLE | fa | ref | PRIMARY,idx_fk_film_id | PRIMARY | 2 | const | 19 | | | 1 | SIMPLE | f | eq_ref | PRIMARY | PRIMARY | 2 | sakila.fa.film_id | 1 | | +----+-------------+-------+--------+------------------------+---------+---------+-------------------+------+-------+ 2 rows in set (0.00 sec)
如何用好关联子查询,很多时候,关联子查询也是一种非常合理,自然,甚至是性能最好的写法。 where in()肯定是不行的,但是 where exists()有时是可以的; 2 union的限制 有时,mysql无法将限制条件从外层"下推"到内层,这使得原本能够限制部分返回结果的条件无法应用到内层查询的优化上。 如果希望union的各个子句能够根据limit只取部分结果集,或者希望能够先拍下再合并结果集的话,就需要在union的各个子句中分别使用这些子句。 如:
(select first_name,last_name from sakila.actor order by last_name) union all (select first_name,last_name from sakila.customer order by last_name) limit 20;会将actor中200条记录和customer中599条记录放在一个临时表中,然后在从临时表中取出前20条; 而
(select first_name,last_name from sakila.actor order by last_name limit 20) union all (select first_name,last_name from sakila.customer order by last_name limit 20) limit 20;
现在中间的临时表中只会包含40条记录。
3 最大值和最小值优化 对于min()和max()查询,mysql的优化做得并不好。mysql> explain select min(actor_id) from actor where first_name=‘PENELOPE‘; +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 1 | SIMPLE | actor | ALL | NULL | NULL | NULL | NULL | 200 | Using where | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ 1 row in set (0.00 sec)
因为在first_name字段上没有索引,因此mysql将会进行一次全表扫描。 如果mysql能够进行主键扫描,那么理论上,mysql读到第一个满足条件的记录的时候,就是我们需要找的最小值了,因为主键时严格按照actor_id字段的大小顺序排序的。但这仅仅是如果,mysql这时只会做全表扫描。 优化min(),使用limit重写SQL:
mysql> explain select actor_id from actor USE INDEX(PRIMARY) where first_name=‘PENELOPE‘ LIMIT 1; +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 1 | SIMPLE | actor | ALL | NULL | NULL | NULL | NULL | 200 | Using where | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ 1 row in set (0.00 sec)看着实验结果,似乎没有使用 主键索引,不知道是什么原因导致.欢迎交流。 4 在同一个表上查询和更新 mysql不允许,对同一张表进行查询和更新:
mysql> update tbl AS outer_tbl set cnt = ( select count(*) from tbl AS inner_tbl where inner_tbl.type = outer_tbl.type ); error:you can‘t specify target table ‘outer_tbl‘ for update in from clause可以使用内连接来绕过这个限制。实际上,这执行了两个查询:一个是子查询中的select语句,另一个是多表关联update,只是关联的表是一个临时表。
mysql> update tbl inner join ( select type,count(*) AS cnt from tbl group by type )AS der using(type) set tbl.cnt = der.cnt;优化器的提示(hint)
mysql> show status like ‘com%‘; +---------------------------+-------+ | Variable_name | Value | +---------------------------+-------+ | Com_admin_commands | 0 | | Com_assign_to_keycache | 0 | | Com_alter_db | 0 | | Com_alter_procedure | 0 | | Com_alter_server | 0 | | Com_alter_table | 0 |com_xxx表示每个xxx语句执行的次数: com_select: 执行select操作的次数,一次查询只累加一次; com_insert: 执行insert操作的次数,对于批量插入的insert操作,只累加一次; com_update: 执行update操作的次数 com_delete: 执行delete操作的次数 上面这些参数对于所有存储引擎的表操作都会进行累计。下面几个参数只是针对innodb存储引擎,累加算法也略有不同。 innodb_rows_read: select查询返回的行数 innodb_rows_inserted: 执行insert操作插入的行数 innodb_rows_updated: 执行update操作更新的行数 innodb_rows_deleted: 执行delete操作删除的行数 通过以上参数,很容易了解当前数据库的应用是以插入更新为主还是以查询操作为主,大致的读写比例是多少; 可以通过com_commit 和 com_rollback 可以知道,事务回滚的比例; 如果比例过高则说明应用编写存在问题; connections: 试图连接mysql服务器的次数 uptime: 服务器工作时间 slow_queries: 慢查询的次数; 2 定位低效 SQL
mysql> explain select b from t where a =1; +----+-------------+-------+------+---------------+------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+-------+------+-------------+ | 1 | SIMPLE | t | ref | a | a | 5 | const | 1 | Using where | +----+-------------+-------+------+---------------+------+---------+-------+------+-------------+ 1 row in set (0.00 sec)
当然explain也可以来查询使用了什么索引;
mysql> select @@have_profiling; +------------------+ | @@have_profiling | +------------------+ | YES | +------------------+查看profiling是否开启,默认关闭:
mysql> select @@profiling; +-------------+ | @@profiling | +-------------+ | 0 | +-------------+ 1 row in set (0.00 sec)
开启profiling:
mysql> set profiling=1; Query OK, 0 rows affected (0.00 sec)通过profile,我们能够更清楚地了解SQL执行的过程。 如何使用:
mysql> select count(*) from payment; +----------+ | count(*) | +----------+ | 16049 | +----------+ 1 row in set (0.02 sec)
通过show profiles,找到对应SQL的 query id;
mysql> show profiles; +----------+------------+------------------------------+ | Query_ID | Duration | Query | +----------+------------+------------------------------+ | 1 | 0.01064275 | select count(*) from payment | | 2 | 0.00048225 | show databases | | 3 | 0.00015000 | show DATABASE() | | 4 | 0.00039975 | show tables | +----------+------------+------------------------------+通过show profile for query id ,分析具体的SQL; 能够看到执行过程中线程的每个状态和消耗的时间;
mysql> show profile for query 4; +----------------------+----------+ | Status | Duration | +----------------------+----------+ | starting | 0.000058 | | checking permissions | 0.000009 | | Opening tables | 0.000050 | | System lock | 0.000008 | | init | 0.000012 | | optimizing | 0.000005 | | statistics | 0.000012 | | preparing | 0.000010 | | executing | 0.000007 | | checking permissions | 0.000132 | | Sending data | 0.000042 | | end | 0.000007 | | query end | 0.000007 | | closing tables | 0.000005 | | removing tmp table | 0.000009 | | closing tables | 0.000006 | | freeing items | 0.000015 | | logging slow query | 0.000005 | | cleaning up | 0.000006 | +----------------------+----------+ 19 rows in set (0.00 sec)在获取到最消耗时间的线程状态后,mysql支持进一步选择all,cpu,block io ,context switch,page faults等明细类型来查看mysql在使用什么资源上耗费了过高的时间。 例如选择查看cup的消耗时间:
mysql> show profile cpu for query 4; +----------------------+----------+----------+------------+ | Status | Duration | CPU_user | CPU_system | +----------------------+----------+----------+------------+ | starting | 0.000058 | 0.000000 | 0.000000 | | checking permissions | 0.000009 | 0.000000 | 0.000000 | | Opening tables | 0.000050 | 0.000000 | 0.000000 | | System lock | 0.000008 | 0.000000 | 0.000000 | | init | 0.000012 | 0.000000 | 0.000000 | | optimizing | 0.000005 | 0.000000 | 0.000000 | | statistics | 0.000012 | 0.000000 | 0.000000 | | preparing | 0.000010 | 0.000000 | 0.000000 | | executing | 0.000007 | 0.000000 | 0.000000 | | checking permissions | 0.000132 | 0.000000 | 0.000000 | | Sending data | 0.000042 | 0.000000 | 0.000000 | | end | 0.000007 | 0.000000 | 0.000000 | | query end | 0.000007 | 0.000000 | 0.000000 | | closing tables | 0.000005 | 0.000000 | 0.000000 | | removing tmp table | 0.000009 | 0.000000 | 0.000000 | | closing tables | 0.000006 | 0.000000 | 0.000000 | | freeing items | 0.000015 | 0.000000 | 0.000000 | | logging slow query | 0.000005 | 0.000000 | 0.000000 | | cleaning up | 0.000006 | 0.000000 | 0.000000 | +----------------------+----------+----------+------------show profile 能够在做SQL优化时帮助我们了解时间都耗费到哪里去了; 而mysql5.6则通过trace文件进一步向我们展示了优化器是如何选择执行计划的。 5 通过trace 分析优化器如何选择执行计划 提供了对SQL的跟踪trace,通过trace文件能够进一步了解为什么优化器选择A执行计划而不选择B执行计划,帮助我们更好地理解优化器的行为。 使用方式: 首先打开trace,设置格式为json,设置trace最大能够使用的内存大小,避免解析过程中因为默认内存过小而不能完整显示。 然后执行select; 最后在,information_schema.optimizer_trace中查看跟踪文件; 索引问题
mysql> show status like ‘Handler_read%‘; +-----------------------+-------+ | Variable_name | Value | +-----------------------+-------+ | Handler_read_first | 1 | | Handler_read_key | 6 | | Handler_read_last | 0 | | Handler_read_next | 16050 | | Handler_read_prev | 0 | | Handler_read_rnd | 0 | | Handler_read_rnd_next | 297 | +-----------------------+-------+ 7 rows in set (0.00 sec)
mysql> analyze table store; +--------------+---------+----------+----------+ | Table | Op | Msg_type | Msg_text | +--------------+---------+----------+----------+ | sakila.store | analyze | status | OK | +--------------+---------+----------+----------+ 1 row in set (0.00 sec)本语句用于分析和存储表的关键字分布,分析的结果将可以使得系统得到准确的统计信息,是的SQL能够生成正确的执行计划。 如果用户感觉实际执行计划并不是预期的执行计划,执行一次分析表可能会解决问题。 检查表:
mysql> check table store; +--------------+-------+----------+----------+ | Table | Op