mysql优化总结
SQL优化目的:
降低响应时间
直接影响用户体验度
降低资源使用率
主要体现在IO和CPU上,网络、内存消耗
优化原则:
1.IN子查询改成JOIN
2.NOT IN子查询改成LEFT JOIN
3.消除无效子查询
4.禁用select *,只读取所需字段
5.LIMIT M, N 大分页修改成JOIN形式
6.大结果集 limit限制,where限制,分页读取
7.使用where xx IN (),IN子句中元素建议小于200
8.多表Join时,注意比较字段类型一致,避免隐式类型转换
9.Where子句右值都用单引号括起,避免隐式类型转换
10.Update/delete根据索引删除,禁用Update/delete … limit N结构SQL
11.禁止对索引列进行数值计算
1.类型转化(避免隐式的类型转换)
mysql> desc tb_shop_order -> ; +----------------------+----------------+------+-----+---------------------+-----------------------------+ | Field | Type | Null | Key | Default | Extra | +----------------------+----------------+------+-----+---------------------+-----------------------------+ | id | varchar(60) | NO | PRI | NULL | | | gorder_id | varchar(60) | NO | MUL | NULL | | | product_type | char(4) | NO | MUL | NULL | | | order_time | timestamp | NO | MUL | 0000-00-00 00:00:00 | | | order_status | tinyint(2) | NO | MUL | NULL | | | order_amount | decimal(12,2) | NO | | 0.00 | | | save_amount | decimal(12,2) | NO | | 0.00 | | | pay_amount | decimal(12,2) | NO | | 0.00 | | | logistics_amount | decimal(12,2) | YES | | NULL | | | fee | decimal(12,2) | NO | | 0.00 | | | original_pay_amount | decimal(12,2) | NO | | 0.00 | | | cost_amount | decimal(12,2) | NO | | 0.00 | | | buy_account_id | varchar(60) | NO | MUL | NULL | | | pre_close_time | timestamp | NO | MUL | 0000-00-00 00:00:00 | | | update_time | timestamp | NO | MUL | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP | | settle_up | tinyint(1) | NO | MUL | NULL | | | success_time | timestamp | NO | | 0000-00-00 00:00:00 | | | merchant_id | int(10) | NO | MUL | NULL | | | merchant_name | varchar(100) | NO | MUL | NULL | | | merchant_account | varchar(100) | YES | | NULL | | | merchant_tel | varchar(60) | YES | | NULL | | | merchant_order_id | varchar(100) | YES | MUL | NULL | | | merchant_amount | decimal(12,2) | YES | | NULL | | | activity_id | int(10) | YES | | NULL | | | activity_type | tinyint(2) | YES | | NULL | | | order_desc | varchar(1000) | YES | | NULL | | | remark | varchar(500) | YES | | NULL | | | can_deliver | tinyint(1) | YES | | NULL | | | can_settle_up | tinyint(1) | YES | | NULL | | | can_refund | tinyint(1) | YES | | NULL | | | misc | varchar(14100) | YES | | NULL | | | key_words | varchar(200) | YES | | NULL | | | delivery_begin_time | timestamp | NO | MUL | 0000-00-00 00:00:00 | | | delivery_end_time | timestamp | NO | MUL | 0000-00-00 00:00:00 | | | relation_id | varchar(60) | YES | | NULL | | | order_ip | varchar(60) | YES | | NULL | | | closed_reason | varchar(60) | YES | | NULL | | | status_desc | varchar(60) | YES | | NULL | | | note | varchar(5120) | YES | | NULL | | | refund_amount | decimal(12,2) | NO | | 0.00 | | | distributor_order_id | varchar(100) | NO | MUL | | | +----------------------+----------------+------+-----+---------------------+-----------------------------+ 41 rows in set (0.00 sec)
定义: `buy_account_id` varchar(60) NOT NULL COMMENT '卡号',
mysql> explain select * from tb_shop_order where buy_account_id = 190000000033703177; +----+-------------+---------------+------------+------+--------------------+------+---------+------+--------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------------+------------+------+--------------------+------+---------+------+--------+----------+-------------+ | 1 | SIMPLE | tb_shop_order | NULL | ALL | ix_order_accountid | NULL | NULL | NULL | 179981 | 10.00 | Using where | +----+-------------+---------------+------------+------+--------------------+------+---------+------+--------+----------+-------------+ 1 row in set, 3 warnings (0.00 sec)
改造之后:ref 走的是const,rows扫描的行数表少,不在是全表扫描了
mysql> explain select * from tb_shop_order where buy_account_id = '190000000033703177'; +----+-------------+---------------+------------+------+--------------------+--------------------+---------+-------+------+----------+-------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------------+------------+------+--------------------+--------------------+---------+-------+------+----------+-------+ | 1 | SIMPLE | tb_shop_order | NULL | ref | ix_order_accountid | ix_order_accountid | 182 | const | 1418 | 100.00 | NULL | +----+-------------+---------------+------------+------+--------------------+--------------------+---------+-------+------+----------+-------+ 1 row in set, 1 warning (0.00 sec)
原则:Where子句右值都用单引号括起,避免隐式类型转换!!!
2.字段计算(使用函数在字段上,不走索引)
mysql> explain select * from tb_shop_order where DATE_FORMAT (order_time,'%Y-%m-%d') = '2016-04-05'; +----+-------------+---------------+------------+------+---------------+------+---------+------+--------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------------+------------+------+---------------+------+---------+------+--------+----------+-------------+ | 1 | SIMPLE | tb_shop_order | NULL | ALL | NULL | NULL | NULL | NULL | 179981 | 100.00 | Using where | +----+-------------+---------------+------------+------+---------------+------+---------+------+--------+----------+-------------+ 1 row in set, 1 warning (0.00 sec)
改造之后:row 扫描的行数变少,不再是全表烧苗,而是范围扫描,而且
Extra Using index
这是性能很高的一种情况。当查询所需的数据可以直接从索引树中检索到时,就会出现。
mysql> explain select * from tb_shop_order where order_time > '2016-04-05 00:00:00' and order_time < '2016-04-05 23:59:59'; +----+-------------+---------------+------------+-------+----------------+----------------+---------+------+------+----------+-----------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------------+------------+-------+----------------+----------------+---------+------+------+----------+-----------------------+ | 1 | SIMPLE | tb_shop_order | NULL | range | ix_order_otime | ix_order_otime | 4 | NULL | 89 | 100.00 | Using index condition | +----+-------------+---------------+------------+-------+----------------+----------------+---------+------+------+----------+-----------------------+ 1 row in set, 1 warning (0.00 sec)
3.避免全表扫描更新
(1) update XXX set batch = '2016-09-27 16:21:01'
where batch = '1970-01-01 08:00:30';
(2) delete from XXX where batch < '2016-09-26 16:21:00';
改造成根据主键批量更新:
select min(pk), max(pk) from table where batch = '1970-01-01 08:00:30';
Update … where batch = '1970-01-01 08:00:30' and pk >= min and pk < i
Update … where batch = '1970-01-01 08:00:30' and pk >= i and pk < j
Update … where batch = '1970-01-01 08:00:30' and pk >= j and pk < max
4.字段传值正确性
错误的拼接:
select * from tb_shop_order where order_time > CONCAT('2016-04-05 00:00:00','00:00:00') and order_time < CONCAT('2016-04-05 23:59:59','00:00:00');
select * from tb_shop_order where order_time > '2016-04-05 00:00:00' and order_time < '2016-04-05 23:59:59./';
5. in OR not in优化
mysql> explain select * from tb_shop_order where buy_account_id ='190000000039541381' and product_type in (select product_type from tb_shop_gorder ); +----+-------------+----------------+------------+------+----------------------------------+--------------------+---------+-------------------------------+-------+----------+----------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+----------------+------------+------+----------------------------------+--------------------+---------+-------------------------------+-------+----------+----------------------------------------+ | 1 | SIMPLE | tb_shop_order | NULL | ref | ix_order_accountid,ix_order_prod | ix_order_accountid | 182 | const | 6 | 100.00 | NULL | | 1 | SIMPLE | tb_shop_gorder | NULL | ref | ix_gorder_ptype | ix_gorder_ptype | 12 | lp.tb_shop_order.product_type | 10333 | 100.00 | Using index; FirstMatch(tb_shop_order) | +----+-------------+----------------+------------+------+----------------------------------+--------------------+---------+-------------------------------+-------+----------+----------------------------------------+ 2 rows in set, 1 warning (0.00 sec)
改造后:
mysql> explain select t1.* from tb_shop_order t1 INNER JOIN tb_shop_gorder t2 on t1.product_type = t2.product_type where t1.buy_account_id ='190000000039541381'; +----+-------------+-------+------------+------+----------------------------------+--------------------+---------+--------------------+-------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+----------------------------------+--------------------+---------+--------------------+-------+----------+-------------+ | 1 | SIMPLE | t1 | NULL | ref | ix_order_accountid,ix_order_prod | ix_order_accountid | 182 | const | 6 | 100.00 | NULL | | 1 | SIMPLE | t2 | NULL | ref | ix_gorder_ptype | ix_gorder_ptype | 12 | lp.t1.product_type | 10333 | 100.00 | Using index | +----+-------------+-------+------------+------+----------------------------------+--------------------+---------+--------------------+-------+----------+-------------+ 2 rows in set, 1 warning (0.00 sec)
mysql> explain select * from tb_shop_order where buy_account_id ='190000000039541381' and product_type not in (select product_type from tb_shop_gorder );+----+--------------------+----------------+------------+----------------+--------------------+--------------------+---------+-------+-------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+--------------------+----------------+------------+----------------+--------------------+--------------------+---------+-------+-------+----------+-------------+ | 1 | PRIMARY | tb_shop_order | NULL | ref | ix_order_accountid | ix_order_accountid | 182 | const | 6 | 100.00 | Using where | | 2 | DEPENDENT SUBQUERY | tb_shop_gorder | NULL | index_subquery | ix_gorder_ptype | ix_gorder_ptype | 12 | func | 10333 | 100.00 | Using index | +----+--------------------+----------------+------------+----------------+--------------------+--------------------+---------+-------+-------+----------+-------------+ 2 rows in set, 1 warning (0.00 sec)
改造后:
mysql> explain select t1.* from tb_shop_order t1 LEFT JOIN tb_shop_gorder t2 on t1.product_type = t2.product_type where t1.buy_account_id ='190000000039541381' and t2.product_type IS NULL; +----+-------------+-------+------------+------+--------------------+--------------------+---------+--------------------+-------+----------+--------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+--------------------+--------------------+---------+--------------------+-------+----------+--------------------------------------+ | 1 | SIMPLE | t1 | NULL | ref | ix_order_accountid | ix_order_accountid | 182 | const | 6 | 100.00 | NULL | | 1 | SIMPLE | t2 | NULL | ref | ix_gorder_ptype | ix_gorder_ptype | 12 | lp.t1.product_type | 10333 | 100.00 | Using where; Not exists; Using index | +----+-------------+-------+------------+------+--------------------+--------------------+---------+--------------------+-------+----------+--------------------------------------+ 2 rows in set, 1 warning (0.00 sec)