本文配合B站学习视频BV1es411u7we使用效果更佳。

1. MySQL版本

主流版本:5.x版

  • 5.0 - 5.1:早期产品的延续,升级维护

  • 5.4 - 5.x:MySQL整合了三方公司的新存储引擎(5.5)

安装:rpm -ivh xxxtar -zxvf xxx.tar.gz

查看已有的相关文件:rpm -qa | grep xxx

安装过程中出现冲突时需将冲突的软件卸载掉:yum -y remove xxxrpm -e --nodeps xxx

验证:mysqladmin --version

服务:

启动服务:service mysql start

关闭服务:service mysql stop

重启服务:service mysql restart

服务开机自启/关闭:chkconfig mysql on/off

登录:mysql

设置初始密码:/usr/bin/mysqladmin -u root password 'new-password'

授权远程连接:

授权:grant all privileges on *.* to '用户名' @'%' identified by '密码';

刷新权限:flush privileges;

开启防火墙服务:systemctl start firewalld.service

开启3306端口:firewall-cmd --zone = public --query-port = 3306/tcp

重新加载防火墙服务:firewall-cmd --reload

数据库存放目录:ps -ef|grep mysql

数据存放目录:datadir=/var/lib/mysql

pid文件目录:pid-file=/var/lib/mysql/chiaki01.pid

进入目录cd /var/lib/mysql,其中mysqlmysql.sock比较重要

MySQL核心目录:

MySQL安装目录:/var/lib/mysql

MySQL配置文件:/usr/share/mysql中的``my-huge.cnfmy-large.cnf`等

MySQL命令目录:/usr/bin,包含mysqladminmusqldump等命令

MySQL启停脚本:/etc/init.d/mysql

MySQL配置文件目录:/etc/my.cnf,不存在就复制过来cp /usr/share/mysql/my-huge.cnf /etc/my.cnf

MySQL编码查询:show variables like '%char%';

统一编码为utf8:进入配置文件my.cnf进行修改

[mysql]:default-character-set=utf8

[client]:default-character-set=utf8

[server]:character_set_server=utf8character_set_client=utf8collation_server=utf8_general_ci

注意事项:修改编码只对修改之后的创建的数据库生效

MySQL清屏:system clear, ctrl+L

备注:5.5以上安装的一些命令

查看初始密码:cat /root/.mysql_sercet

安装完初始登录:mysql -uroot -p并输入密码

登录成功设置密码安全策略并修改密码(5.5以上):

  • 改变密码等级:set global validate_password_policy=0;
  • 改变密码最小长度:set global validate_password_length=4;
  • 修改密码:SET PASSWORD = PASSWORD('密码');

授权:grant all privileges on *.* to '用户名' @'%' identified by '密码';

刷新权限:flush privileges;

开放远程连接(关闭防火墙服务或者开放防火墙3306端口)

  • 关闭防火墙服务:systemctl stop firewalld.service
  • 开启防火墙服务:systemctl start firewalld.service
  • 开启3306端口:firewall-cmd --zone = public --query-port = 3306/tcp
  • 重新加载防火墙服务:firewall-cmd --reload
  • 查看服务:firewall-cmd --list-all

CentOS7安装MySQL5.7:https://www.cnblogs.com/Mr-Rshare/p/11799945.html

2. MySQL底层原理

逻辑分层(自顶向下)

连接层:提供与客户端连接的服务

服务层:提供各种用户使用的接口(select等);提供SQL优化器(MySQL Query Optimizer)

引擎层:提供了各种存储数据的方式(InnoDB和MyISAM等)

存储层:存储数据

引擎区别

InnoDB:事务优先(适合高并发操作;行锁)(5.5及以上默认引擎)

MyISAM:性能优先(不支持事务;表锁)

引擎相关SQL语句

查询数据库支持的引擎:show engines

查询当前使用的引擎:show variables like '%storage_engine%';

创建数据库对象时指定引擎:

  1. CREATE TABLE tb (
  2. id INT PRIMARY KEY AUTO_INCREMENT,
  3. NAME VARCHAR (5),
  4. dept VARCHAR (5)
  5. ) ENGINE = MYISAM AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8 ;

指定数据库对象的引擎:show table status like "tb" ;

3. SQL优化

为什么要SQL优化

性能低

执行时间长

等待时间长

SQL语句欠佳(连接查询)

索引失效

服务器参数设置不合理(缓冲、线程)

SQL解析

编写过程:select dinstinct...from...join...on...where...group by...having...order by...limit...

解析过程:from...on...join...where...group by...having...select dinstinct...order by...limit...

https://www.cnblogs.com/annsshadow/p/5037667.html

SQL优化之索引简介

索引(Index):是帮助MySQL高效获取数据的数据结构(B树(MySQL默认)和哈希索引)。

B树中的2-3树:3层B树可以存放上百万条数据

B+树:B树的一种,是MySQL使用的索引结构,数据全部存放在叶节点中。B+树中给查询任意的数据次数为n次,即B+树的高度。

索引的弊端:

  • 索引本身很大,可以存放在内存/硬盘(通常为硬盘)。
  • 索引不是所有情况均适用:少量数据;频繁更新的字段;很少使用的字段。
  • 索引确实可以提高查询效率,但是会降低增删改的效率。

索引的优势:

  • 提高查询效率(降低IO使用率)
  • 降低CPU使用率(...order by age desc,因为B树索引已经是一个排好序的结构)

4. 索引

索引分类

单值索引:单列,一个表可以有多个单值索引

唯一索引:在单值索引基础上,字段的值不可重复,一般为id

复合索引:多个列构成的索引(相当于二级目录)

索引创建

创建索引(一):create 索引类型 索引名 on 表名(字段)

单值索引:create index dept_index on tb(dept);

唯一索引:create unique index name_index on tb(name);

复合索引:create index dept_name_index on tb(dept,name);

创建索引(二):alter table 表名 add 索引类型 索引名(字段)

单值索引:alter table tb add index dept_index(dept);

唯一索引:alter table tb add unique index name_index(name);

复合索引:alter table tb add index dept_name_index(dept,name);

注意事项

如果一个字段设置为主键,则该字段默认就是主键索引,与唯一索引较类似,但存在区别。

主键索引不能是null,唯一索引可以为null。

删除及查询索引

删除索引:drop index 索引名 on 表名

查询索引:show index from 表名

5. SQL性能问题及优化

分析SQL的执行计划:explain,模拟SQL优化器执行SQL语句,使开发人员清除编写的SQL状况。SQL优化器会干扰优化。

Explian查询执行计划

查询执行计划:explain + SQL语句

explain select * from tb;

  • id:编号
  • select_type:查询类型
  • table:表
  • type:类型
  • possible_keys:预测用到的索引
  • key:实际使用的索引
  • ken_len:实际使用索引的长度
  • ref:表之间的引用关系
  • rows:通过索引查询到的数据记录数
  • Extra:额外信息

案例:

建表并插入记录:

  1. USE myDB;
  2. -- 试验中先不设置主键和外键
  3. CREATE TABLE teacherCard (
  4. tcid INT,
  5. tcdesc VARCHAR (30)
  6. ) ;
  7. CREATE TABLE teacher (
  8. tid INT ,
  9. tname VARCHAR (20),
  10. tcid INT
  11. ) ;
  12. CREATE TABLE course (
  13. cid INT ,
  14. cname VARCHAR (20),
  15. tid INT
  16. ) ;
  17. INSERT INTO teacherCard VALUES(1,'tzdesc');
  18. INSERT INTO teacherCard VALUES(2,'twdesc');
  19. INSERT INTO teacherCard VALUES(3,'tldesc');
  20. INSERT INTO teacher VALUES(1,'tz',1);
  21. INSERT INTO teacher VALUES(2,'tw',2);
  22. INSERT INTO teacher VALUES(3,'tl',3);
  23. INSERT INTO course VALUES(1,'java',1);
  24. INSERT INTO course VALUES(2,'html',1);
  25. INSERT INTO course VALUES(3,'sql',2);
  26. INSERT INTO course VALUES(4,'web',3);

explain + SQL语句:

练习:查询课程编号为2或教师证编号为3的老师信息

  1. EXPLAIN
  2. SELECT
  3. t.*
  4. FROM
  5. teacher t,
  6. course c,
  7. teacherCard tc
  8. WHERE t.tid = c.tid
  9. AND t.tcid = tc.tcid
  10. AND (c.cid = 2
  11. OR tc.tcid = 3) ;

id:编号

  • id值相同,从上往下顺序执行。

执行顺序t(3)-tc(3)-c(4)(括号中表示表中的记录数)。现向teacher标值再插入3条数据,并执行同样的SQL语句。

  1. INSERT INTO teacher VALUES(4,'ta',4);
  2. INSERT INTO teacher VALUES(5,'tb',5);
  3. INSERT INTO teacher VALUES(6,'tc',6);
  4. EXPLAIN
  5. SELECT
  6. t.*
  7. FROM
  8. teacher t,
  9. course c,
  10. teacherCard tc
  11. WHERE t.tid = c.tid
  12. AND t.tcid = tc.tcid
  13. AND (c.cid = 2
  14. OR tc.tcid = 3) ;

上图结果中,执行顺序变为:tc(3)-c(4)-t(6)

表的执行顺序因表中记录数的改变而改变,其原因在于:笛卡尔积记录数最小的表优先查询,使中间笛卡尔积最小。

验证:删除course表中的两条记录,再次执行查看结果:

  1. DELETE FROM course WHERE cid > 2;
  2. EXPLAIN
  3. SELECT
  4. t.*
  5. FROM
  6. teacher t,
  7. course c,
  8. teacherCard tc
  9. WHERE t.tid = c.tid
  10. AND t.tcid = tc.tcid
  11. AND (c.cid = 2
  12. OR tc.tcid = 3) ;

上图结果中,执行顺序变为:c(2)-tc(3)-t(6)

  • id值不同,id值越大的越优先执行(本质:在嵌套子查询时,先查内层,在查外层)。

练习:查询教授SQL课程的老师的描述。

  1. EXPLAIN
  2. SELECT
  3. tc.tcdesc
  4. FROM
  5. teacherCard tc,
  6. course c,
  7. teacher t
  8. WHERE t.tid = c.tid
  9. AND t.tcid = tc.tcid
  10. AND c.cname = 'SQL' ;

将以上多表查询转为子查询形式

  1. EXPLAIN
  2. SELECT
  3. tc.tcdesc
  4. FROM
  5. teacherCard tc
  6. WHERE tcid =
  7. (SELECT
  8. t.tcid
  9. FROM
  10. teacher t
  11. WHERE t.tid =
  12. (SELECT
  13. c.tid
  14. FROM
  15. course c
  16. WHERE c.cname = 'SQL')) ;

id值越大的先执行,执行顺序为:c(2)-t(6)-tc(3)

  • id值有相同有不同:id值越大越优先,如果id值相同,从上往下依次执行。

综合:子查询+多表:查询教授SQL课程的老师的描述。

  1. EXPLAIN
  2. SELECT
  3. t.tname,
  4. tc.tcdesc
  5. FROM
  6. teacher t,
  7. teacherCard tc
  8. WHERE t.tcid = tc.tcid
  9. AND t.tid =
  10. (SELECT
  11. c.cid
  12. FROM
  13. course c
  14. WHERE c.cname = 'SQL') ;

上图结果中,执行顺序变为:c(2)-tc(3)-t(6)

select_type:查询类型

PRIMARY:包含子查询SQL中的主查询(最外层)

SUBQUERY:包含子查询SQL中的子查询(非最外层)

SIMPLE:简单查询(不包含子查询和union连接查询)

  1. EXPLAIN SELECT * FROM teacher t;

DERIVED:衍生查询(使用到了临时表)

UNION:见下例

UNION RESULT:告知开发者哪些表存在UNION查询,见下例

  • 在FROM子查询中只有一张表:查询教课老师编号是1或2的课程信息。

    1. EXPLAIN
    2. SELECT
    3. cr.cname
    4. FROM
    5. (SELECT
    6. *
    7. FROM
    8. course
    9. WHERE tid IN (1, 2)) cr ;

  • 在FROM子查询中,如果table1 union table2,则table1(左表)就是DERIVED,而table2就是UNION:查询教课老师编号是1或2的课程信息。

    1. EXPLAIN
    2. SELECT
    3. cr.cname
    4. FROM
    5. (SELECT
    6. *
    7. FROM
    8. course
    9. WHERE tid = 1
    10. UNION
    11. SELECT
    12. *
    13. FROM
    14. course
    15. WHERE tid = 2) cr ;

type:索引类型、类型

常用type:

system > const > eq_ref > ref > range > index > all,性能依次降低,其中system和const只是理想情况,实际能达到最高为ref。要对type进行优化的前提是要有索引

  • system:只有一条数据的系统表或衍生表只有一条数据的主查询。

    1. -- 创建test01
    2. CREATE TABLE test01 (tid INT, tname VARCHAR (20));
    3. -- test01表中插入1条数据
    4. INSERT INTO test01 VALUES(1,'a');
    5. -- 添加主键索引(优化type的前提)
    6. ALTER TABLE test01 ADD CONSTRAINT tid_pk PRIMARY KEY(tid);
    7. -- 分析执行计划(衍生表只有1条数据)
    8. EXPLAIN SELECT * FROM (SELECT * FROM test01) t WHERE t.tid = 1;

    上图中衍生表的type为system。

  • const:仅能查到一条数据的SQL,用于Primary key或Unique索引(与索引类型有关)

    1. EXPLAIN SELECT t.tid FROM test01 t WHERE t.tid = 1;

    上图中由于仅能查到1条数据,同时用于主键索引,因此type为const。

    1. -- 删除主键索引
    2. ALTER TABLE test01 DROP PRIMARY KEY;
    3. -- 添加单值索引
    4. ALTER TABLE test01 ADD INDEX test01_index(tid);
    5. -- 再次分析执行计划
    6. EXPLAIN SELECT t.tid FROM test01 t WHERE t.tid = 1;

    上图中仅能查到1条数据,但用于一般单值索引,因此type不是const。

  • eq_ref:唯一性索引,即对于每个索引键的查询,返回唯一匹配行数据(有且仅有一个),常见于唯一索引主键索引

    1. -- teacherCard表设置主键
    2. ALTER TABLE teacherCard ADD CONSTRAINT tcid_pk PRIMARY KEY(tcid);
    3. -- teacher表设置唯一键约束
    4. ALTER TABLE teacher ADD CONSTRAINT uk_tcid UNIQUE INDEX(tcid);
    5. -- 连接查询
    6. SELECT t.tcid FROM teacher t, teacherCard tc WHERE t.tcid = tc.tcid;
    7. -- 分析执行计划
    8. EXPLAIN SELECT t.tcid FROM teacher t, teacherCard tc WHERE t.tcid = tc.tcid;
    9. -- 查询teacher
    10. SELECT * FROM teacher;

    上图中type的结果不是eq_ref,其原因在于不满足有且仅有一个,因为在teacher表中的tid唯一索引的返回的结果有6条,而连接查询返回的结果只有3条,所以不满足条件。

    删除后teacher表的后三条数据再次分析执行计划:

    1. -- 删除后三条数据
    2. DELETE FROM teacher WHERE tid > 3;
    3. -- 分析执行计划
    4. EXPLAIN SELECT t.tcid FROM teacher t, teacherCard tc WHERE t.tcid = tc.tcid;

    上图结果中type为eq_ref。以上SQL,用到的索引是t.tcid,即teacher表中的tcid字段。如果teacher表的数据个数和链接连接查询的数据个数一直,才能满足eq_ref级别。

  • ref:非唯一性索引:对于每隔索引键的索引,返回匹配的所有行。

    1. -- 数据准备使得teacher表中tname列中存在重复tz
    2. INSERT INTO teacher VALUES(4,'tz',4);
    3. INSERT INTO teacherCard VALUES(4,'tz2222');
    4. -- 创建teachertname列的索引
    5. ALTER TABLE teacher ADD INDEX tname_index(tname);
    6. -- 使用tname作为索引进行查询
    7. SELECT * FROM teacher t WHERE t.tname = 'tz';
    8. -- 分析执行计划
    9. EXPLAIN SELECT * FROM teacher t WHERE t.tname = 'tz';

    上图的结果中type为ref。

  • range:检索指定范围的行,where后是一个范围查询(between...and...,>,< 等),其中范围查询使用in时,有可能失效转为无索引all。

    1. -- teacher表的tid列添加索引
    2. ALTER TABLE teacher ADD INDEX tid_index(tid);
    3. -- 分析执行计划
    4. EXPLAIN SELECT t.* FROM teacher t WHERE t.tid IN (1, 2); -- 失效 变为all
    5. EXPLAIN SELECT t.* FROM teacher t WHERE t.tid < 3; -- range
    6. EXPLAIN SELECT t.* FROM teacher t WHERE t.tid > 3; -- range
    7. EXPLAIN SELECT t.* FROM teacher t WHERE t.tid BETWEEN 1 AND 2; -- range

  • index:查询全部索引中的数据

    1. -- 查询teacher表中tid列的所有数据(确保tid列已有索引) 只需扫描索引表
    2. EXPLAIN SELECT t.tid FROM teacher t; -- typeindex

  • all:查询全部中的数据

    1. -- course表没有索引 需要全表扫描
    2. EXPLAIN SELECT c.cid FROM course c; -- typeall

总结:

  • system/const:结果只有一条数据。
  • eq_ref:结果多条,但每条数据有且仅有一条(不能为0也不能为多)。
  • ref:结果多条名单每条数据是0或多(唯一则为eq_ref)。

possible_keys:可能用的索引

key:实际用的索引

possible_keys是一种预测,不准。如果possible_keys和key是null,表示没有使用索引。

Eg1:

  1. -- 确保添加索引
  2. -- 将未添加索引的字段添加索引
  3. ALTER TABLE course ADD INDEX cname_index(cname);
  4. -- 分析执行计划
  5. EXPLAIN
  6. SELECT
  7. t.tname,
  8. tc.tcdesc
  9. FROM
  10. teacher t,
  11. teacherCard tc
  12. WHERE t.tcid = tc.tcid
  13. AND t.tid =
  14. (SELECT
  15. c.cid
  16. FROM
  17. course c
  18. WHERE c.cname = 'SQL') ;

Eg2:

  1. -- 确保添加索引
  2. -- 分析执行计划
  3. EXPLAIN
  4. SELECT
  5. tc.tcdesc
  6. FROM
  7. teacherCard tc,
  8. course c,
  9. teacher t
  10. WHERE t.tid = c.tid
  11. AND t.tcid = tc.tcid
  12. AND c.cname = 'SQL' ;

key_len:索引的长度

作用:用于判断复合索引是否被完全使用。

常识:

  • utf8:1个字符3个字节
  • gbk:1个字符2个字节
  • latin:1个字符1个字节

固定长度的索引类型:

Eg1-1:

  1. -- 创建test_kl表用于key_len的试验
  2. CREATE TABLE test_kl (NAME CHAR(20) NOT NULL DEFAULT ''); -- name字段非空
  3. -- 添加单值索引
  4. ALTER TABLE test_kl ADD INDEX name_index(NAME);
  5. -- 分析执行计划
  6. EXPLAIN SELECT * FROM test_kl WHERE NAME = ''; -- key_len = 60

结果key_len = 60。原因是在utf8中一个char类型字符占3个字节,所以60 = 3 * 20

Eg1-2:

  1. -- 添加字段
  2. ALTER TABLE test_kl ADD COLUMN name1 CHAR(20); -- name1字段可以为空
  3. -- 添加单值索引
  4. ALTER TABLE test_kl ADD INDEX name1_index(name1);
  5. -- 分析执行计划
  6. EXPLAIN SELECT * FROM test_kl WHERE name1 = ''; -- key_len = 61

结果key_len = 61如果索引字段可以为null,则会使用1个字节作为标识。

Eg1-3:

  1. -- 删除索引
  2. DROP INDEX name_index ON test_kl;
  3. DROP INDEX name1_index ON test_kl;
  4. -- 添加复合索引
  5. ALTER TABLE test_kl ADD INDEX name_name1_index(NAME,name1);
  6. -- 分析执行计划 使用name1字段
  7. EXPLAIN SELECT * FROM test_kl WHERE name1 = ''; -- key_len = 121
  8. -- 分析执行计划 使用name字段
  9. EXPLAIN SELECT * FROM test_kl WHERE name = ''; -- key_len = 60

使用复合索引查询时,使用name字段导致复合索引没有被完全使用,使得key_len = 60,使用name1字段使得复合索引被完全使用,key_len = 121

可变长度的索引类型:

Eg2:

  1. -- 添加新字段
  2. ALTER TABLE test_kl ADD COLUMN name2 VARCHAR(20); -- 可以为null
  3. -- 添加单值索引
  4. ALTER TABLE test_kl ADD INDEX name2_index(name2);
  5. -- 分析执行计划
  6. EXPLAIN SELECT * FROM test_kl WHERE name2 = ''; -- key_len = 63

结果key_len = 63。原因是63 = 3 * 20 + 1 (标识null) + 2 (标值可变长度)

ref:表之间的引用关系

作用:指明当前表所参照的字段。注意与type中的ref值区分。

Eg:

  1. -- 分析执行计划
  2. EXPLAIN
  3. SELECT
  4. *
  5. FROM
  6. course c,
  7. teacher t
  8. WHERE c.tid = t.tid
  9. AND t.tname = 'tw' ;

上图结果中,where后的条件包含两部分c.tid = t.tid以及t.tname = 'tw'。对于前一部分,c.tid参照的字段为t表中的t.tid,由于c.tid未设置索引,所以ref的值为null;对于后一部分,t.tname参照的字段为'tw',是一个给定的常量,所以ref的值为const

t表中的c.tid添加索引后重新分析执行计划:

  1. -- course表的tid字段添加索引
  2. ALTER TABLE course ADD INDEX tid_index(tid);
  3. -- 分析执行计划
  4. EXPLAIN
  5. SELECT
  6. *
  7. FROM
  8. course c,
  9. teacher t
  10. WHERE c.tid = t.tid
  11. AND t.tname = 'tw' ;

添加索引后,c.tid = t.tid条件中c表的c.tid字段参照了t表的t.tid字段,所以ref的值为myDB.t.tid

rows:通过索引查询的记录数

Eg:

  1. -- 分析执行计划
  2. EXPLAIN
  3. SELECT
  4. *
  5. FROM
  6. course c,
  7. teacher t
  8. WHERE c.tid = t.tid
  9. AND t.tname = 'tz' ;
  10. -- 查询
  11. SELECT * FROM course c, teacher t WHERE c.tid = t.tid AND t.tname = 'tz';
  12. -- 查询c
  13. SELECT * FROM course;
  14. -- 查询t
  15. SELECT * FROM teacher;

上图结果中,c表通过索引查询得到的记录数为2条,所以c表的rows值为2;虽然执行查询语句得到了t表的2条记录,但是其是重复的,真正通过t表索引查询得到的记录只有1条,所以t表的rows值为1。

Extra:额外信息

常见信息:

  • Using filesort:性能消耗大;需要“额外”的一次排序(查询),常见于order by语句中。

    Eg1:单值索引

    1. -- 创建新表
    2. -- 创建新表并添加单值索引
    3. CREATE TABLE test02 (
    4. a1 CHAR(3),
    5. a2 CHAR(3),
    6. a3 CHAR(3),
    7. INDEX idx_a1 (a1),
    8. INDEX idx_a2 (a2),
    9. INDEX idx_a3 (a3)
    10. ) ;
    11. -- 分析执行计划
    12. EXPLAIN SELECT * FROM test02 WHERE a1 = '' ORDER BY a1;
    13. EXPLAIN SELECT * FROM test02 WHERE a1 = '' ORDER BY a2;

    上图结果中,按字段a1排序时Extra的值为Using where,按字段a2排序时Extra的值为Using where; Using filesort。由于查询的字段是a1,当按a1排序时,就按照查出来的结果排序即可,然而当按a2排序时就需要以a2为字段进行一次额外的查询,然后将查询的结果排序,所以Extra的信息中包含Using filesort

    小结:对于单索引,如果排序和查找是同一字段,则不会出现Using filesort的情况,反之则会出现。为了避免这一问题,可以采用如下方法:where哪些字段就order by哪些字段。

    Eg2:复合索引(满足最左前缀原则

    1. -- 删除单值索引
    2. DROP INDEX idx_a1 ON test02;
    3. DROP INDEX idx_a2 ON test02;
    4. DROP INDEX idx_a3 ON test02;
    5. -- 添加复合索引
    6. ALTER TABLE test02 ADD INDEX idx_a1_a2_a3(a1,a2,a3);
    7. -- 分析执行计划
    8. EXPLAIN SELECT * FROM test02 WHERE a1 = '' ORDER BY a3; -- Using filesort
    9. EXPLAIN SELECT * FROM test02 WHERE a2 = '' ORDER BY a3; -- Using filesort
    10. EXPLAIN SELECT * FROM test02 WHERE a1 = '' ORDER BY a2;

    小结:对于复合索引,为了避免出现Using filesort,where和order by按照复合索引的顺序使用,不要跨列或无序使用。

  • Using temporary:性能损耗大,用到了临时表,常见于group by语句中。

    Eg1:

    1. -- 分析执行计划
    2. EXPLAIN SELECT a1 FROM test02 WHERE a1 IN ('1','2','3') GROUP BY a1;
    3. EXPLAIN SELECT a1 FROM test02 WHERE a1 IN ('1','2','3') GROUP BY a2; -- Using temporary

    上图结果中,以a1索引对字段a1进行查询却按字段a2进行分组,导致需要用到临时表,Extra中出现Using temporary。要避免这种情况可以采用如下方法:查询哪些列就根据那些列group by。

  • Using index:性能提升;覆盖索引。原因:出现Using index,说明不读取原文件,只从索引文件中获取数据,即不需要回表查询。只要是用到的列全部都在索引中,就是覆盖索引。

    Eg1:test02表中存在复合索引(idx_a1_a2_a3);正例

    1. -- 分析执行计划
    2. EXPLAIN SELECT a1, a2 FROM test02 WHERE a1 = '' OR a2 = '';

    上图结果中,由于使用的字段a1和字段a2均包含在复合索引中,是覆盖索引,因此Extra中出现Using index

    Eg2:反例

    1. -- 删除复合索引
    2. DROP INDEX idx_a1_a2_a3 ON test02;
    3. -- 添加字段a1a2的复合索引
    4. ALTER TABLE test02 ADD INDEX idx_a1_a2(a1, a2);
    5. -- 分析执行计划
    6. EXPLAIN SELECT a1, a3 FROM test02 WHERE a1 = '' OR a3 = '';

    上图结果中,使用了字段a1a3进行查询,而复合索引中不包含字段a3,因此不是覆盖索引,所以Extra中不会出现Using index

    Eg3: 覆盖索引会对其他属性产生影响

    1. -- 分析执行计划
    2. EXPLAIN SELECT a1, a2 FROM test02 WHERE a1 = '' OR a2 = '';
    3. EXPLAIN SELECT a1, a2 FROM test02;

    如果使用覆盖索引(Using index),会对possible_keyskey造成影响:

    • 若没有where,则索引只出现在key中;
    • 如果没有索引,则索引出现在possible_keys和key中。
  • Using where:可能需要回表查询

    1. -- 分析执行计划
    2. EXPLAIN SELECT a1, a3 FROM test02 WHERE a3 = ''; -- 需要回表查询

    上图结果中,字段a3不在索引中,因此需要回表查询,Extra的信息为Using where

    1. -- 分析执行计划
    2. EXPLAIN SELECT a1, a2 FROM test02 WHERE a1 = '' OR a2 = '';

    上图结果中使用了覆盖索引,所以Extra中包含了Using index,但同时Extra的信息中还出现了Using where,其实此时并未发生回表查询。Using indexUsing where一起出现时一定不发生回表查询。

    备注:

  • Impossible WHERE:where子句永远为false

    1. -- 分析执行计划
    2. EXPLAIN SELECT * FROM test02 WHERE a1 = 'x' AND a1 = 'y'; -- where子句永远为false,出现Impossible where

6. 优化案例

单表优化

准备:

  1. -- 创建book
  2. CREATE TABLE book (
  3. bid INT PRIMARY KEY,
  4. NAME VARCHAR (20) NOT NULL,
  5. authorId INT NOT NULL,
  6. publicId INT NOT NULL,
  7. typeId INT NOT NULL
  8. ) ;
  9. -- 插入数据
  10. INSERT INTO book VALUES(1, 'tjava', 1, 1, 2);
  11. INSERT INTO book VALUES(2, 'tc', 2, 1, 2);
  12. INSERT INTO book VALUES(3, 'wx', 3, 2, 1);
  13. INSERT INTO book VALUES(4, 'math', 4, 2, 3);

查询:typeId =2或typeId=3且authorID=1的bid

  1. -- 查询
  2. SELECT bid FROM book WHERE typeId IN (2, 3) AND authorId = 1;
  3. -- 分析执行计划
  4. EXPLAIN SELECT bid FROM book WHERE typeId IN (2, 3) AND authorId = 1;
  5. EXPLAIN SELECT bid FROM book WHERE typeId IN (2, 3) AND authorId = 1 ORDER BY typeId DESC;

从结果可以看出,该查询语句性能较低,需要优化。

优化1:添加复合索引

  1. -- 添加索引
  2. ALTER TABLE book ADD INDEX idx_bid_tid_aid(bid, typeId, authorId);
  3. -- 分析执行计划
  4. EXPLAIN SELECT bid FROM book WHERE typeId IN (2, 3) AND authorId = 1 ORDER BY typeId DESC;

从结果看出,type由all提升为index,Extra的信息中出现Using index,但是Using filesort仍然存在,继续优化。

优化2:按照SQL的实际解析顺序调整索引顺序,重新添加索引。

  1. -- 删除索引
  2. DROP INDEX idx_bid_tid_aid ON book;
  3. -- 按解析顺序添加索引
  4. ALTER TABLE book ADD INDEX idx_tid_aid_bid(typeId, authorId, bid);
  5. -- 分析执行计划
  6. EXPLAIN SELECT bid FROM book WHERE typeId IN (2, 3) AND authorId = 1 ORDER BY typeId DESC;

上图结果中,Extra中的信息中只有Using indexUsing where,是覆盖索引,不需要回表查询,效率提升。同时覆盖索引对possible_keyskey产生了影响。

优化3:提升type级别。因为使用范围查新时in有时会失效,因此交换索引的顺序,同时改变查询语句where子句的顺序。

  1. -- 删除索引
  2. DROP INDEX idx_tid_aid_bid ON book;
  3. -- 按解析顺序添加索引
  4. ALTER TABLE book ADD INDEX idx_aid_tid_bid(authorId, typeId, bid);
  5. -- 分析执行计划
  6. EXPLAIN SELECT bid FROM book WHERE authorId = 1 AND typeId IN (2, 3) ORDER BY typeId DESC;

上图结果中,type由index提升至ref,性能进一步提升。

小结:

  • 最左前缀原则,保持索引的定义和使用的顺序一致性;
  • 索引需要逐步优化;
  • 将含in的范围查询放到where子句最后防止失效。

两表优化

准备:

  1. -- 创建表
  2. CREATE TABLE teacher2 (tid INT PRIMARY KEY, cid INT NOT NULL) ;
  3. CREATE TABLE course2 (cid INT, cname VARCHAR (20)) ;
  4. -- 插入数据
  5. INSERT INTO course2 VALUES(1,'java');
  6. INSERT INTO course2 VALUES(2,'python');
  7. INSERT INTO course2 VALUES(3,'kotlin');
  8. INSERT INTO teacher2 VALUES(1,2);
  9. INSERT INTO teacher2 VALUES(2,1);
  10. INSERT INTO teacher2 VALUES(3,3);

Eg:左连接添加索引进行优化

小表驱动大表:where 小表.x = 大表.x

索引建立在经常使用的字段上

  1. -- 不加索引分析执行计划
  2. EXPLAIN SELECT * FROM teacher2 t LEFT OUTER JOIN course2 c ON t.cid = c.cid WHERE c.cname = 'java';
  3. -- 添加索引
  4. ALTER TABLE teacher2 ADD INDEX index_teacher2_cid(cid);
  5. -- 添加索引分析执行计划
  6. EXPLAIN SELECT * FROM teacher2 t LEFT OUTER JOIN course2 c ON t.cid = c.cid WHERE c.cname = 'java';

上图结果中,添加索引后t表的type由all提升至index,同时t表的Extra信息为Using indexc表的Extra中出现Using join buffer表明MySQL引擎使用了连接缓存。

Eg:继续添加索引

  1. -- 添加索引
  2. ALTER TABLE course2 ADD INDEX index_course2_cname(cname);
  3. -- 分析执行计划
  4. EXPLAIN SELECT * FROM teacher2 t LEFT OUTER JOIN course2 c ON t.cid = c.cid WHERE c.cname = 'java';

上图结果中,c表和t表的type均提升至ref

三表优化

原则:

  • 小表驱动大表
  • 索引建立在经常查询的字段上

7. 避免索引失效的原则

原则:

  • 复合索引

    • 复合索引不要跨列或无序使用(最左前缀原则);
    • 复合索引尽量使用全索引匹配。
  • 不要在索引上进行任何操作(计算、函数、类型转换),否则索引失效。

    Eg:

    1. -- 查看索引
    2. SHOW INDEX FROM book;
    3. -- 分析执行计划
    4. EXPLAIN SELECT * FROM book WHERE authorId = 1 AND typeId = 2;
    5. EXPLAIN SELECT * FROM book WHERE authorId = 1 AND typeId * 2 = 2;
    6. EXPLAIN SELECT * FROM book WHERE authorId * 2 = 1 AND typeId * 2 = 2;
    7. EXPLAIN SELECT * FROM book WHERE authorId * 2 = 1 AND typeId = 2;

    上图结果中,通过key_len可以清楚地看出对索引进行操作导致索引失效。值得注意地是,复合索引中,如果左侧失效,其右侧全部失效(最左前缀)。

  • 复合索引中不能使用不等于(!= ,<>)或is null(is not null),否则自身以及右侧索引全部失效。由于SQL优化器的原因,大多情况下,范围查询(>, <, in)之后的索引失效。

    Eg:

    1. -- 删除添加索引
    2. DROP INDEX idx_aid_tid_bid ON book;
    3. ALTER TABLE book ADD INDEX idx_authorId(authorId);
    4. ALTER TABLE book ADD INDEX idx_typeId(typeId);
    5. -- 分析执行计划
    6. EXPLAIN SELECT * FROM book WHERE authorId = 1 AND typeId = 2;
    7. EXPLAIN SELECT * FROM book WHERE authorId <> 1 AND typeId = 2;
    8. EXPLAIN SELECT * FROM book WHERE authorId <> 1 AND typeId <> 2;

    由于MySQL服务层中SQL优化器的存在,SQL优化是一种概率层面的优化。实际中是否使用优化,需要通过explain进行推测。因此在第一次查询结果中,理想情况下应该是使用idx_authorIdidx_typeId两个索引,但实际中只使用了idx_authorId。第二次查询中由于对idx_authorId使用了不等于操作,使得idx_authorId索引失效,而使用了idx_typeId索引。第三次查询中,两个索引都进行了不等于操作,使得索引都失效。

    SQL优化器影响的例子:

    1. -- 删除添加索引
    2. DROP INDEX idx_authorId ON book;
    3. DROP INDEX idx_typeId ON book;
    4. ALTER TABLE book ADD INDEX idx_aid_tid(authorId, typeId);
    5. -- 分析执行计划
    6. -- 复合索引全部使用
    7. EXPLAIN SELECT * FROM book WHERE authorId = 1 AND typeId = 2;
    8. -- 复合索引全部失效
    9. EXPLAIN SELECT * FROM book WHERE authorId > 1 AND typeId = 2;
    10. -- 复合索引全部使用
    11. EXPLAIN SELECT * FROM book WHERE authorId = 1 AND typeId > 2;
    12. -- 复合索引部分失效
    13. EXPLAIN SELECT * FROM book WHERE authorId < 1 AND typeId = 2;
    14. -- 复合索引全部失效
    15. EXPLAIN SELECT * FROM book WHERE authorId < 4 AND typeId = 2;

  • 尽量使用覆盖索引(Using index)。

  • like尽量以常量开头,不要以%开头,否则索引失效。

    1. -- 查看索引
    2. SHOW INDEX FROM teacher;
    3. -- 分析执行计划
    4. EXPLAIN SELECT * FROM teacher WHERE tname LIKE 'x%';
    5. EXPLAIN SELECT * FROM teacher WHERE tname LIKE '%x%';
    6. EXPLAIN SELECT tname FROM teacher WHERE tname LIKE '%x%';

    上图结果中由于在like后面以%开头导致索引失效。如果必须要like后面以%开头,可以使用覆盖索引(Using index)。

  • 尽量不要使用类型转换(显示、隐式),否则索引失效。

    1. -- 分析执行计划
    2. EXPLAIN SELECT * FROM teacher WHERE tname = 'abc';
    3. EXPLAIN SELECT * FROM teacher WHERE tname = 123;

    上图结果中,程序底层将123转换为'123',即进行了类型转换,因此索引失效。

  • 尽量不要使用or,否则索引失效。

    1. -- 分析执行计划
    2. EXPLAIN SELECT * FROM teacher WHERE tname = '' AND tcid > 1;
    3. EXPLAIN SELECT * FROM teacher WHERE tname = '' OR tcid > 1;

    上图结果中,在使用了or之后,索引失效。

8. 一些其他的优化方法

EXIST和IN

exist和in:如果主查询的数据集大,则使用in,效率高;如果子查询的数据集大,则使用exist,效率高。

exist语法:将主查询的结果放到子查询中进行条件校验(看子查询是否有数据,如果有数据,则校验成功),如果校验成功则保留查询结果,否则不保留。

  1. SELECT tname FROM teacher WHERE EXISTS (SELECT * FROM teacher); -- 有效
  2. SELECT tname FROM teacher WHERE EXISTS (SELECT * FROM teacher WHERE tid = 60); -- 失效

ORDER BY

order by优化:常出现Using filesort,Using filesort有两种排序算法:双路排序、单路排序(根据IO的次数)。

MySQL4.1之前,默认使用双路排序:扫描2次磁盘 - ① 从磁盘读取排序字段并在缓冲区中进行排序;②扫描其他字段。MySQL4.1之后,为了减少IO访问次数消耗性能,默认使用单路排序:只扫描一次磁盘 - 一次读取全部字段并在缓冲区进行排序,但存在隐患(实际上不一定真的是一次IO,可能是多次IO)。原因在于如果数据量特别大则无法将所有数据一次性读取完毕,因此会进行分片多次读取。

注意:

  • 单路排序比双路排序会占用更多的缓冲区(buffer);

  • 单路排序在使用时,如果数据量特别大,可以考虑扩增buffer的容量大小

    1. -- 调整buffer的容量大小 单位byte
    2. SET max_length_for_sort_data = 1024;
  • 如果需要排序的数据(order by 后的字段)总大小超过了max_length_for_sort_data定义的字节数,那么MySQL会自动由单路排序切换为双路排序。

提高order by查询效率的策略:

  • 选择使用单路排序或双路排序,调整buffer容量的大小;
  • 尽量避免select * ...语句;
  • 复合索引不要跨列使用,避免出现Using filesort
  • 尽量保证全部排序字段的排序一致性(都是升序或都是降序)。

9. SQL排查

慢查询日志

MySQL提供的一种日志记录,用于记录MySQL中响应时间超过阀值的SQL语句(long_query_time:默认10秒)。

慢查询日志默认关闭,在开发调优是建议打开,最终部署时关闭。

检查是否开启了慢查询日志以及开启慢查询日志:

  1. -- 检查是否开启
  2. SHOW VARIABLES LIKE '%slow_query_log%';
  3. -- 临时开启,重启MySQL服务失效
  4. SET GLOBAL slow_query_log = 1;
  5. -- 永久开启
  6. -- 在/etc/my.cnf配置文件中的[mysqld]后追加配置:
  7. -- slow_query_log = 1
  8. -- slow_query_log_file = /vaar/lib/mysql/localhost-slow.log

查询并修改慢查询阀值:

  1. -- 查询慢查询阀值
  2. SHOW VARIABLES LIKE '%long_query_time%';
  3. -- 临时设置慢查询阀值
  4. -- 设置完毕后重新登录生效
  5. SET GLOBAL long_query_time = 3;
  6. -- 永久开启
  7. -- 在/etc/my.cnf配置文件中的[mysqld]后追加配置:
  8. -- long_query_time = 3

Eg:

  1. -- 查询慢查询阀值
  2. SHOW VARIABLES LIKE '%long_query_time%';
  3. -- 查询线程休眠4
  4. SELECT SLEEP(4);
  5. -- 查看响应时间超过慢查询阀值的SQL条数
  6. SHOW GLOBAL STATUS LIKE '%slow_queries%';

查看具体的慢SQL:

  • 通过慢查询日志可以查看具体的SQL语句:cat /var/lib/mysql/localhost-slow.log

  • mysqldumpslow工具查看慢SQL,可以通过一些过滤条件找到需要定位的慢SQL

    s:排序方式;r:逆序;l:锁定时间;g:正则表达式

    1. -- 多增加几条慢SQL
    2. SELECT SLEEP(5);
    3. SELECT SLEEP(3);
    4. SELECT SLEEP(3);
    5. SELECT SLEEP(3);

    Eg1:获取返回记录最多的3个慢SQL

    mysqldumpslow -s r -t 3 /var/lib/mysql/localhost-slow.log

    Eg2:获取访问次数最多的3个慢SQL

    mysqldumpslow -s c -t 3 /var/lib/mysql/localhost-slow.log

    Eg3:按时间排序,前十条包含left join查询语句的SQL

    mysqldumpslow -s t -t 10 -g "LEFT JOIN" /var/lib/mysql/localhost-slow.log

10. 分析海量数据

模拟海量数据

利用存储过程(无return)/存储函数(有return):

  1. -- 创建新数据库并切换
  2. CREATE DATABASE testdata;
  3. USE testdata;
  4. -- 创建新表
  5. CREATE TABLE dept (
  6. dno INT PRIMARY KEY DEFAULT 0,
  7. dname VARCHAR (20) NOT NULL DEFAULT '',
  8. loc VARCHAR (20) DEFAULT ''
  9. ) ENGINE = INNODB DEFAULT CHARSET = utf8 ;
  10. CREATE TABLE emp (
  11. eid INT PRIMARY KEY,
  12. ename VARCHAR (20) NOT NULL DEFAULT '',
  13. job VARCHAR (20) NOT NULL DEFAULT '',
  14. deptno INT NOT NULL DEFAULT 0
  15. ) ENGINE = INNODB DEFAULT CHARSET = utf8 ;
  16. -- 通过存储函数插入海量数据
  17. -- 创建随机字符串模拟员工名称
  18. DELIMITER $
  19. CREATE FUNCTION randstring(n INT) RETURNS VARCHAR (255)
  20. BEGIN
  21. DECLARE all_str VARCHAR (100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' ;
  22. DECLARE return_str VARCHAR (255) DEFAULT '' ;
  23. DECLARE i INT DEFAULT 0 ;
  24. WHILE
  25. i < n DO SET return_str = CONCAT(
  26. return_str,
  27. SUBSTRING(all_str, FLOOR(RAND() * 52) + 1, 1)
  28. ) ;
  29. SET i = i + 1 ;
  30. END WHILE ;
  31. RETURN return_str ;
  32. END $
  33. -- 创建随机数字模拟编号
  34. DELIMITER $
  35. CREATE FUNCTION ran_num () RETURNS INT (5)
  36. BEGIN
  37. DECLARE i INT DEFAULT 0 ;
  38. SET i = FLOOR(RAND() * 100) ;
  39. RETURN i ;
  40. END $
  41. -- 通过存储过程向emp表插入海量数据
  42. DELIMITER $
  43. CREATE PROCEDURE insert_emp (
  44. IN eid_start INT (10),
  45. IN data_times INT (10)
  46. )
  47. BEGIN
  48. DECLARE i INT DEFAULT 0 ;
  49. SET autocommit = 0 ;
  50. REPEAT
  51. INSERT INTO emp
  52. VALUES
  53. (
  54. eid_start + i,
  55. randstring (5),
  56. 'other',
  57. ran_num ()
  58. ) ;
  59. SET i = i + 1 ;
  60. UNTIL i = data_times
  61. END REPEAT ;
  62. COMMIT ;
  63. END $
  64. -- 通过存储过程向dept表插入海量数据
  65. DELIMITER $
  66. CREATE PROCEDURE insert_dept (
  67. IN dno_start INT (10),
  68. IN data_times INT (10)
  69. )
  70. BEGIN
  71. DECLARE i INT DEFAULT 0 ;
  72. SET autocommit = 0 ;
  73. REPEAT
  74. INSERT INTO dept
  75. VALUES
  76. (
  77. dno_start + i,
  78. randstring (6),
  79. randstring (8)
  80. ) ;
  81. SET i = i + 1 ;
  82. UNTIL i = data_times
  83. END REPEAT ;
  84. COMMIT ;
  85. END $
  86. -- 插入数据
  87. CALL insert_emp(1000,800000);
  88. CALL insert_dept(10,30);
  89. -- 验证
  90. SELECT COUNT(1) FROM emp;
  91. SELECT COUNT(1) FROM dept;

可能会出现报错:

  • SQL syntax:SQL语法有错,需修改SQL语句

  • This function has none of DETERMINISTIC......:慢查询日志冲突,可以按如下方式解决

    1. -- 临时解决
    2. SHOW VARIABLES LIKE '%log_bin_trust_function_creators%';
    3. SET GLOBAL log_bin_trust_function_creators = 1;
    4. -- 永久解决
    5. -- 永久开启
    6. -- 在/etc/my.cnf配置文件中的[mysqld]后追加配置:
    7. -- log_bin_trust_function_creators = 1

分析海量数据

  • 利用profiles:当profiling开启后会记录全部SQL语句的相关信息(id,执行时间和SQL语句)。缺点在于只能看多总执行时间,不能看到各个硬件消耗的时间。

    1. -- 查看
    2. SHOW VARIABLES LIKE '%profiling%';
    3. -- 使用
    4. SHOW PROFILES;
    5. -- 开启
    6. SET profiling = ON;
    7. -- 查看
    8. SHOW VARIABLES LIKE '%profiling%';
    9. -- 使用
    10. SHOW PROFILES;
    11. -- 查询
    12. SELECT COUNT(1) FROM dept;
    13. -- 使用
    14. SHOW PROFILES;

  • 精确分析:SQL诊断

    1. -- SQL诊断
    2. SHOW PROFILE ALL FOR QUERY 2;
    3. SHOW PROFILE cpu, block io FOR QUERY 2;

  • 全局查询日志:记录profileing开启后的全部SQL语句(全局的记录操作仅仅在调优和开发过程中打开即可,在最终部署时一定关闭),在mysql.general_log表中可以查看日志。

    1. -- 查看
    2. SHOW VARIABLES LIKE '%general_log%';
    3. -- 将全部的SQL记录在表中
    4. SET GLOBAL general_log = ON;
    5. SET GLOBAL log_output = 'table';
    6. -- 查看
    7. SHOW VARIABLES LIKE '%general_log%';
    8. -- 查询
    9. SELECT * FROM emp;
    10. SELECT COUNT(*) FROM emp;
    11. -- 查看日志
    12. SELECT * FROM mysql.general_log;
    13. -- 也可以将全部的SQL记录到文件
    14. SET GLOBAL general_log_file = ON;
    15. SET GLOBAL log_output = 'file';
    16. SET GLOBAL general_log_file = '/tmp/general.log';
    17. -- 查询
    18. SELECT COUNT(1) FROM dept;

    查看日志文件:cat /tmp/general.log

11. 锁机制

解决因资源共享而造成的并发问题。

分类:

  • 操作类型:

    • 读锁(共享锁):对同一数据,多个读操作可以同时进行,互不干扰。
    • 写锁(互斥锁):如果当前写操作没有完成,则无法进行其他的读操作和写操作。
  • 操作范围:
    • 表锁:一次性对整张表加锁。如MyISAM存储引擎使用表锁,开销小,加锁块;无死锁;但锁的范围大,容易发生锁冲突,并发度低。
    • 行锁:一次性对一条数据加锁。如InnoDB存储引擎使用行锁,开销大,加锁慢;容易出现死锁;锁的范围较小,不易发生锁冲突,并发度高(发生高并发问题:脏读、修改丢失、不可重复读和幻读)。
    • 页锁

表锁(MyISAM)

加读锁

  1. -- 建表设置为MyISAM引擎
  2. CREATE TABLE tablelock (
  3. id INT PRIMARY KEY AUTO_INCREMENT,
  4. NAME VARCHAR (20)
  5. ) ENGINE MYISAM ;
  6. -- 插入数据
  7. INSERT INTO tablelock VALUES(NULL,'a1');
  8. INSERT INTO tablelock VALUES(NULL,'a2');
  9. INSERT INTO tablelock VALUES(NULL,'a3');
  10. INSERT INTO tablelock VALUES(NULL,'a4');
  11. INSERT INTO tablelock VALUES(NULL,'a5');
  12. -- 查看加锁的表
  13. SHOW OPEN TABLES;
  14. -- 加读锁
  15. LOCK TABLE tablelock READ;

会话1(加锁的会话):

如果会话1对表加了read锁,那么会话1可以对该表进行读操作,不能进行写操作;会话1对其他表既不可以进行读操作也不可以进行写操作。换句话说,若会话1对数据库中的一个表加了read锁,那么会话1只能进行对加锁表的读操作。

会话2(其他会话):

其他会话能对加锁表进行读操作,不能进行写操作,可以对其他表进行读操作和写操作。

加锁的会话 其他会话
加锁表的读操作
加锁表的写操作 × √ 需要等待锁释放
其他表的读操作 ×
其他表的写操作 ×

加写锁:

  1. -- 释放锁
  2. UNLOCK TABLES;
  3. -- 加写锁
  4. LOCK TABLE tablelock WRITE;
加锁的会话 其他会话
加锁表的读操作 √ 需要等待锁释放
加锁表的写操作 √ 需要等待锁释放
其他表的读操作 ×
其他表的写操作 ×

MySQL表级锁的锁模式:

MyISAM在执行查询语句(SELECT)前会自动给涉及的所有表加read锁,在执行更新操作(DML)前会自动给涉及的表加write锁。所以对MyISAM表进行操作会出现以下情况:

  • 对MyISAM表的读操作(加读锁),不会阻塞其他进程(会话)对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其他进程的写操作;
  • 对MyISAM表的写操作(加写锁),会阻塞其他进程(会话)对同一表的读和写操作,只有当写锁释放后,才会执行其他进程的读写操作。

分析表锁定:

  1. -- 查看加锁的表
  2. SHOW OPEN TABLES;

In_use:当其值为1时,表示被加了锁。

  1. -- 分析表锁定的严重程度
  2. SHOW STATUS LIKE 'table%';

Table_locks_immediate:可能获取到的锁的数量

Table_lock_waited:需要等待的表锁数(如果该值越大,说明存在越大的锁竞争)。

一般建议

计算比值n = Table_locks_immediate / Table_lock_waited ,若n > 5000,建议采用InnoDB引擎,否则采用MyISAM引擎。

行锁(InnoDB)

InnoDB存储引擎默认使用行锁

  1. -- 创建表
  2. CREATE TABLE linelock (
  3. id INT PRIMARY KEY AUTO_INCREMENT,
  4. NAME VARCHAR (20)
  5. ) ENGINE = INNODB DEFAULT CHARSET = utf8 ;
  6. -- 插入数据
  7. INSERT INTO linelock VALUES(NULL, '1');
  8. INSERT INTO linelock VALUES(NULL, '2');
  9. INSERT INTO linelock VALUES(NULL, '3');
  10. INSERT INTO linelock VALUES(NULL, '4');
  11. INSERT INTO linelock VALUES(NULL, '5');

两个会话进行操作

会话1:

  1. -- 关闭自动提交
  2. SET autocommit = 0;
  3. -- 会话1进行写操作
  4. INSERT INTO linelock VALUES(6,'a6');

会话2:

  1. -- 关闭自动提交
  2. SET autocommit = 0;
  3. -- 会话2对同一条数据进行写操作
  4. UPDATE linelock SET NAME = 'ax' WHERE id = 6;

会话1结果:

会话2结果:

行锁机制:

  • 如果会话1对某条数据进行DML操作(关闭自动提交的情况下),则其他操作必须等待会话或事务结束后(commit/rollback)后才能进行操作。
  • 表锁通过UNLOCK TABLES;释放锁,行锁通过事务解锁(commit/rollback)。
  • 行锁一次锁一行数据,因此操作不同行的数据互不干扰。

行锁的注意事项:

  • 如果没有索引,则行锁会转为表锁。(注意回顾索引失效的情况)
  • 行锁的一种特殊情况(间隙锁):值在范围内,但却不存在。MySQL会自动给间隙加间隙锁。实际中where子句后面加范围查询时,实际加锁的范围就是查询的范围(不是数据库表中实际的值)。

行锁小结:

  • InnoDB默认采用行锁;
  • 缺点在于相比表锁性能损耗大,优点在于并发能力强以及效率高。
  • 建议高并发使用InnoDB存储引擎,否则用MyISAM存储引擎。

分析行锁定:

  1. SHOW STATUS LIKE '%innodb_row_lock%';

Innodb_row_lock_current_waits:当前正在等待锁的数量

Innodb_row_lock_time:从系统启动到现在锁定的总时长

Innodb_row_lock_time_avg:从系统启动到现在锁定的平均时长

Innodb_row_lock_time_max:从系统启动到现在锁定的最大时长

Innodb_row_lock_waits:从系统启动到现在等待的次数

查询时加锁:

通过for update对query语句进行加锁。

  1. -- 开启事务
  2. BEGIN
  3. -- 会话1进行查询
  4. SELECT * FROM linelock WHERE id = 2 FOR UPDATE; -- 加锁
  5. -- 会话2进行更新
  6. UPDATE linelock SET NAME = '222' WHERE id = 2; -- 等待锁释放

12. 主从复制

什么是主从复制

主从复制,是用来建立一个和主数据库完全一样的数据库环境,称为从数据库;

主数据库一般是准实时的业务数据库。

主从复制的作用

  • 实时灾备,用于故障切换:做数据的热备,作为后备数据库,主数据库服务器故障后,可切换到从数据库继续工作,避免数据丢失。
  • 架构扩展,提升机器性能:业务量越来越大,I/O访问频率过高,单机无法满足,此时做多库的存储,降低磁盘I/O访问的频率,提高单个机器的I/O性能。
  • 读写分离,避免影响业务:读写分离使数据库能支撑更大的并发。在报表中尤其重要。由于部分报表sql语句非常的慢,导致锁表,影响前台服务。如果前台使用master,报表使用slave,那么报表sql将不会造成前台锁,保证了前台速度。

主从复制的原理

  • 数据库有个bin-log二进制文件,记录了所有sql语句。

  • 目标就是把主数据库的bin-log文件的sql语句复制过来。

  • 使其在从数据库的relay-log重做日志文件中再执行一次这些sql语句即可。

  • 主从复制配置具体需要三个线程:

    • binlog输出线程:每当有从库连接到主库的时候,主库都会创建一个线程然后发送binlog内容到从库。在从库里,当复制开始的时候,从库就会创建以下两个线程进行处理。
    • 从库I/O线程:当START SLAVE语句在从库开始执行之后,从库创建一个I/O线程,该线程连接到主库并请求主库发送binlog里面的更新记录到从库上。从库I/O线程读取主库的binlog输出线程发送的更新并拷贝这些更新到本地文件,其中包括relay log文件。
    • 从库SQL线程:从库创建一个SQL线程,这个线程读取从库I/O线程写到relay log的更新事件并执行。
  • 对于每一个主从复制的连接,都有三个线程。拥有多个从库的主库为每一个连接到主库的从库创建一个binlog输出线程,每一个从库都有它自己的I/O线程和SQL线程。

主从复制的问题及解决方法

存在问题:

  • 主库宕机后,数据可能丢失;
  • 从库只有一个sql Thread,主库写压力大,复制很可能延时。

解决方法:

  • 半同步复制:解决数据丢失的问题
  • 并行复制:解决从库复制延迟的问题

MySQL进阶之MySQL索引以及索引优化的更多相关文章

  1. mysql进阶(四)mysql中select

    mysql中select * for update 注:  FOR UPDATE 仅适用于InnoDB,且必须在事务区块(BEGIN/COMMIT)中才能生效. 作用 锁定该语句所选择到的对象.防止在 ...

  2. (3)MySQL进阶篇SQL优化(索引)

    1.索引问题 索引是数据库优化中最常用也是最重要的手段之一,通过索引通常可以帮助用户解决大多数 的SQL性能问题.本章节将对MySQL中的索引的分类.存储.使用方法做详细的介绍. 2.索引的存储分类 ...

  3. mysql进阶(二十六)MySQL 索引类型(初学者必看)

    mysql进阶(二十六)MySQL 索引类型(初学者必看)   索引是快速搜索的关键.MySQL 索引的建立对于 MySQL 的高效运行是很重要的.下面介绍几种常见的 MySQL 索引类型.   在数 ...

  4. MySQL进阶篇(02):索引体系划分,B-Tree结构说明

    本文源码:GitHub·点这里 || GitEE·点这里 一.索引简介 1.基本概念 首先要明确索引是什么:索引是一种数据结构,数据结构是计算机存储.组织数据的方式,是指相互之间存在一种或多种特定关系 ...

  5. MySQL进阶篇(03):合理的使用索引结构和查询

    本文源码:GitHub·点这里 || GitEE·点这里 一.高性能索引 1.查询性能问题 在MySQL使用的过程中,所谓的性能问题,在大部分的场景下都是指查询的性能,导致查询缓慢的根本原因是数据量的 ...

  6. 【夯实Mysql基础】MySQL性能优化的21个最佳实践 和 mysql使用索引

    本文地址 分享提纲: 1.为查询缓存优化你的查询 2. EXPLAIN 你的 SELECT 查询 3. 当只要一行数据时使用 LIMIT 1 4. 为搜索字段建索引 5. 在Join表的时候使用相当类 ...

  7. MySQL索引分析与优化

    1.MySQL能够在name的索引中查找“Mike”值,然后直接转到数据文件中相应的行,准确地返回该行的 peopleid(999).在这个过程中,MySQL只需处理一个行就可以返回结果.如果没有“n ...

  8. 数据库 MySQL进阶之索引

    数据库的索引非常重要,基本面试数据库的问题都在索引上,所以这里小编整理出来,一方面为了自己复习,一方面也方便大家. 一,索引前传 在了解数据库索引之前,首先有必要了解一下数据库索引的数据结构基础,那么 ...

  9. mysql进阶(二十七)数据库索引原理

    mysql进阶(二十七)数据库索引原理 前言   本文主要是阐述MySQL索引机制,主要是说明存储引擎Innodb.   第一部分主要从数据结构及算法理论层面讨论MySQL数据库索引的数理基础.    ...

随机推荐

  1. 算法数据结构 | 只要30行代码,实现快速匹配字符串的KMP算法

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是算法数据结构专题的第29篇文章,我们来聊一个新的字符串匹配算法--KMP. KMP这个名字不是视频播放器,更不是看毛片,它其实是由Kn ...

  2. 微服务迁移记(五):WEB层搭建(2)-SpringSecurity集成

    一.redis搭建 二.WEB层主要依赖包 三.FeignClient通用接口 以上三项,参考<微服务迁移记(五):WEB层搭建(1)> 接下来,集成SpringSecruity,实现用户 ...

  3. 11-14序列化模块之json、pickle、shelve

    序列化的目的 1.以某种存储形式使自定义对象持久化: 2.将对象从一个地方传递到另一个地方. 3.使程序更具维护性. 序列化--转向一个字符串数据类型序列--及时字符串 何处用到: 数据存储 网络上传 ...

  4. PHP is_scalar() 函数

    is_scalar() 函数用于检测变量是否是一个标量.高佣联盟 www.cgewang.com 标量变量是指那些包含了 integer.float.string 或 boolean 的变量,而 ar ...

  5. dsu on tree详解

    这个算法还是挺人性化的,没有什么难度 就是可能看起来有点晕什么的. 大体 思想是 利用重链刨分来优化子树内部的查询. 考虑一个问题要对每个子树都要询问一次.我们暴力显然是\(n^2\)的. 考虑一下优 ...

  6. 100% 展示 MySQL 语句执行的神器-Optimizer Trace

    在上一篇文章<用Explain 命令分析 MySQL 的 SQL 执行>中,我们讲解了 Explain 命令的详细使用.但是它只能展示 SQL 语句的执行计划,无法展示为什么一些其他的执行 ...

  7. Access to XMLHttpRequest at xxxx from origin ‘null‘ has been blocked by CORS policy:

    使用前后端分离的方式创建web项目的时候出现问题: 这是因为 ajax 请求的对应的域在本地的一个文件路径,比如在D盘的某个文件夹,这里存放的都是前端文件: 但是对应的服务器是 localhost 的 ...

  8. 菊长说丨一文读懂MySQL4种事务隔离级别

    经常提到数据库的事务,那你知道数据库还有事务隔离的说法吗,事务隔离还有隔离级别,那什么是事务隔离,隔离级别又是什么呢?今天我们就找菊长去,请他帮大家梳理一下这些各具特色的事务隔离级别,咱走着~~~ 点 ...

  9. Bytom 储蓄分红 DAPP 开发指南

    储蓄分红DAPP 储蓄分红合约简介 储蓄分红合约指的是项目方发起了一个锁仓计划(即储蓄合约和取现合约),用户可以在准备期自由选择锁仓金额参与该计划,等到锁仓到期之后还可以自动获取锁仓的利润.用户可以在 ...

  10. 搭建 springboot selenium 网页文件转图片环境

    1. 环境准备 需要有 chrome 浏览器 + chrome driver + selenium 客户端 离线 chrome 下载地址 # 64位 linux 系统 https://dl.googl ...