MYSQL是一个关系型数据库管理系统,由瑞典MYSQL AB公司开发,目前属于Oracle公司
mysql内核
sql优化工程师
mysql服务器的优化
各种参数常量设定
查询语句优化
主从复制
软硬件升级
容灾备份
sql编程
wget -i -c http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm
yum -y install mysql57-community-release-el7-10.noarch.rpm
yum -y install mysql-community-server
启动mysql:
systemctl start mysqld.service
查看是否启动成功:
systemctl status mysqld.service
查看生成的密码:
grep "password" /var/log/mysqld.log
用刚刚的密码登录mysql:
mysql -u root -p密码
关闭密码长度限制:
set global validate_password_policy=0;
set global validate_password_length=1;
修改密码:
ALTER USER 'root'@'localhost' IDENTIFIED BY '自己想设置的密码';
顺便打开远程访问:GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '密码' WITH GRANT OPTION;
修改字符集(my.cnf):character-set-server=utf8
路径 | 解释 | 备注 |
---|---|---|
/var/lib/mysql | mysql数据库文件存放位置 | mysql数据文件(数据库、表)存放位置 |
/etc/my.cnf | mysql配置文件 | |
/usr/bin | 相关命令目录 |
主要用于主从复制 log-bin=路径
默认是关闭的,记录严重的警告和错误信息,每次启动和关闭的详细信息等 log-error=路径
默认关闭,记录查询的sql语句,如果开启会减低mysql的整体性能。
frm:存放表结构
myd:myisam 数据
myi:myisam 索引
idb:innodb 数据和索引
1. 连接层
2. 服务层
3. 引擎层
4. 存储层
查看已提供的引擎:show engines;
查看默认存储引擎:show variables like '%storage_engine%';'
对比项 | MYISAM | InnoDB |
---|---|---|
主外键 | 不支持 | 支持 |
事务 | 不支持 | 支持 |
行表锁 | 表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作 | 行锁,操作时只锁定某一行,不对其他行有影响,适合高并发 |
缓存 | 只缓存索引,不缓存真实数据 | 不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响 |
表空间 | 小 | 大 |
关注点 | 性能 | 事务 |
默认安装 | Y | Y |
1. 查询语句写的烂
2. 索引失效
单值 idx_user_name
复合 idx_user_nameEmail
3. 关联查询太多join(设计缺陷或不得已的需求)
4. 服务器调优及各个参数设置(缓冲、线程数等)
手写
按照自己想法
机读顺序
from 笛卡尔积
on 主表保留
join 不符合ON也添加
Where 非聚合 非select别名
group by 改变对表的引用
having 只作用分组后
select distinct
order by 可使用select别名
limit rows offset
索引:排好序的快速查找数据结构
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上。
B+Tree实现
即一个索引只包含单个列,一个表可以有多个单列索引
索引列的值必须唯一,但允许有空值
一个索引包含多个列
CREATE [UNIQUE] INDEX indexName ON mytable(columnname(length));
ALTER mytable ADD [UNIQUE] INDEX indexName ON (columnname(length));
DROP INDEX indexName ON mytable;
SHOW INDEX FROM table_name\G;
提供mysql认为最优的执行计划(不一定是DBA认为最优的)
CPU在饱和的时候一般发生在数据装入内存或从磁盘上读取数据的时候
磁盘I/O瓶颈发生在装入数据远大于内存容量的时候
top,free,iostat和vmstat来查看系统的性能
查看执行计划
作用
表的读取顺序
数据读取操作的操作类型
哪些索引可以使用
哪些索引被实际使用
表之间的引用
每张表有多少行被优化器查询
字段解释
id: id相同,执行顺序由上到下
id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先执行
select_type: PRIMARY 查询中若包含任何复杂的子查询,最外层查询被标记为PRIMARY
SUBQUERY 在select或者where列表中包含了子查询
SIMPLE 简单的select查询,查询中不包含子查询或者UNION
DERIVED 在from列表中包含的子查询被标记为DERIVED(衍生)mysql会递归执行这些子查询,把结果放在临时表里
UNION 若第二个select出现在UNION之后,则被标记为UNION;若UNION包含在from子句的子查询中,外层select将被标记为DERIVED
UNION RESULT 从UNION表获取结果的select
type:system>const>eq_ref>ref>range>index>All
system 表中只有一行记录(等于系统表),这是const类型的特例,平时不会出现
const 表示通过索引一次就找到了,const用于比较primary或者unique索引。因为只匹配一行数据,如将主键置于where列表中,mysql就能将该查询转换为一个常量
eq_ref 唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描
ref 非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,他可能找到多个复合条件的行,索引他应该属于查找和扫描的混合体
range 只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引一般就是在你的where语句中出现between、<、>、in等的查询 这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,结束于索引的某一点,不用扫描全部索引
index Full Index Scan,index与All区别为index类型只遍历索引树。这通常比All快,因为索引文件通常比数据文件小。(也就是说虽然all和index都是读全表,但index是从索引中读取的,而all是从磁盘中读的)
possible_keys:显示可能应用在这张表中的索引,一个或者多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用
key:实际使用的索引。如果为null,则没有使用索引 查询中若使用了覆盖索引,则该索引出现在key列表中
key_len:表示索引中使用的字节数,可通过该列计算查询中使用的索引长度。在不损失精确性的情况下,长度越短越好 key_len显示的值为索引字段的最大可能长度,并非实际长度,即key_len是根据表定义计算而得,不是通过表内检索出的
ref:显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或者常量被用于查找索引列上的值
rows: 根据表统计信息及索引选用情况,大致估算出找到所需记录所需的读取行数
extra: 1. Using filesort 说明mysql会对数据使用一个外部的索引顺序,而不是按照表内的索引顺序进行读取 mysql无法使用索引完成的排序操作称为“文件排序”
2. Using temporary 使用了临时表保存中间结果,mysql在对查询结果排序时使用了临时表。常见于排序order by或者分组查询group by
3. Using index 表示相应的select操作中使用了覆盖索引(Covering Index),避免访问表的数据行 如果同时出现using where,表示索引被用来执行索引键值的查找 如果没有同时出现using where,表明索引用来读取数据而非执行查找动作
4. using where 表明使用了where过滤
5. using join buffer 使用了连接缓存
6. impossible where where子句的值总是false,不能用来获取任何元组
7. select tables optimized away 在没有groupby子句的情况下,基于索引优化MIN/MAX操作或者对于Myisam存储引擎优化count(*)操作,不必等到执行阶段再进行计算,查询执行计划生产的阶段即完成
8. distinct 优化distinct操作,在找到第一个匹配的元组后即停止找同样值的动作
范围后面索引失效,建索引的时候没有用范围字段
小表驱动大表
join的时候,与两表情况类似
全值匹配我最爱
最佳左前缀法则
不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效转向全表扫描
存储引擎不能使用索引中范围条件右边的列
尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少select *
mysql在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描
is null,is not null也无法使用索引
like以通配符开头(’%abc’)mysql索引失效会变成全表扫描的操作
解决like’%字符串%'时索引不被使用的方法:覆盖索引
字符串不加单引号索引失效
少用or,用它来连接时会索引失效
定值、范围还是排序,一般order by是给个范围 group by基本上都需要进行排序,会有临时表产生,order by 定值不影响,倒序filesort
对于单值索引,尽量选择针对当前query过滤性更好的索引
在选择组合索引的时候,当前query中过滤性最好的字段在索引字段排序中,位置越靠前越好。
在选择组合索引的时候,尽量选择可以能够包含当前query中的where子句中更多字段的索引
尽可能提供分析统计信息和调整query的写法来表达选择合适索引的目的
全值匹配我最爱,最左前缀要遵循;
带头大哥不能死,中间兄弟不能断;
索引列上少计算,范围之后全失效;
Like百分写最右,覆盖索引不写星;
exists:将主查询的数据,放到子查询中做条件验证,根据验证结果(true或false)来决定主查询的结果是否保留。
order by子句,尽量使用index方式排序,避免使用filesort方式排序
尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀
mysql支持二种方式排序,filesort和index,index效率高,它指mysql扫描索引本身完成排序。filesort方式效率低。
order by满足两种情况,会使用index方式排序:
如果不在索引列上,filesort有两种算法:双路排序和单路排序
双路排序(mysql4.1之前):两次扫描磁盘,最终得到数据,读取行指针和orderby列,对他们进行排序。从磁盘取排序字段,在buffer进行排序,再去磁盘取其他字段。
单路排序:从磁盘读取查询需要的索有列,按照orderby列在buffer对他们进行排序,然后扫描排序后的列表进行输出,他的效率更快一些,避免了二次读取数据。
单路问题:sort_buffer容量过小,会多次操作,产生大量io。修改max_length_for_sort_data
提高orderby查询速度:
key a_b_c(a,b,c)
orderby能使用最左前缀
如果where使用索引的最左前缀定义为常量,则order by能使用索引
不能使用索引进行排序
group by实质是先排序后进行分组,遵照索引的最佳左前缀
增大max_length_for_sort_data参数和sort_buffer_size参数
能用where 不要写having限定
其他和order by一致
超过阙值(long_query_time)值的sql
mysql默认没有开启慢查询日志,如果不是调优需要,一般不建议启动该参数
默认:
SHOW VARIABLES LIKE '%slow_query_log%';
开启:
SET GLOBAL slow_query_log=1;
只对当前数据库生效
长期开启:
my.cnf
slow_query_log=1;
slow_query_log_file=/var/lib/mysql/slow_query_log.log
默认时间
SHOW VARIABLES LIKE '%long_query_time%'; //10秒
设置慢的阙值时间
set global long_query_time=3;//重新开启会话后生效
查看慢查询语句条数:
SHOW GLOBAL STATUS LIKE '%Slow_queries%';
my.cnf配置:
slow_query_log=1;
slow_query_log_file=/var/lib/mysql/slow_query_log.log
long_query_time=3;
log_output=FILE
日志分析工具mysqldumpslow
s:表示按照何种方式排序
c:访问次数
l:锁定时间
r:返回记录
t:查询时间
al:平均锁定时间
ar:平均返回记录数
at:平均查询时间
t:即为返回前面多少条的数据
g:后面搭配一个正则匹配模式,大小写不敏感
返回记录集最多的10个SQL
mysqldumpslow -s r -t 10 /var/lib/mysql/slow_query_log.log
返回访问次数最多的10个SQL
mysqldumpslow -s c -t 10 /var/lib/mysql/slow_query_log.log
按照时间排序的前10条里面含有左连接的查询语句
mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/slow_query_log.log
设置参数
set global log_bin_trust_function_creators=1;
查看设置
show variables like '%log_bin_trust_function_creators%';
创建表:
CREATE TABLE dept
(
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
deptno INT unsigned NOT NULL DEFAULT 0,
dname VARCHAR(20) not null default "",
loc VARCHAR (13) not null default ""
)
CREATE TABLE emp
(
id int unsigned primary key auto_increment,
empno mediumint unsigned not null default 0,
ename varchar(20) not null default "",
job varchar(9) not null default "",
mgr mediumint unsigned not null default 0,
hiredate date not null,
sal decimal(7,2) not null,
comm decimal(7,2) not null,
deptno mediumint unsigned not null default 0
)ENGINE=INNODB DEFAULT CHARSET=utf8;
delimiter $$
create function ran_string(n int) returns varchar(255)
begin
declare chars_str varchar(100) default 'abcdefghijklmnopqrstuvwxyzQWERTYUIOPASDFGHJKLZXCVBNM';
declare return_str varchar(255) default '';
declare i int default 0;
while i < n do
set return_str = concat(return_str,substring(chars_str,floor(1+rand()*52),1));
set i=i+1;
end while;
return return_str;
end $$
delimiter $$
create function rand_num() returns int(5)
begin
declare i int default 0;
set i=floor(100+rand()*10);
return i;
end $$
delimiter $$
create procedure insert_emp(in start int(10),in max_num int(10))
begin
declare i int default 0;
set autocommit = 0;
repeat
set i = i+1;
insert into emp(empno,ename,job,mgr,hiredate,sal,comm,deptno) values((start+i),ran_string(6),'salesman',0001,curdate(),2000,400,rand_num());
until i=max_num
end repeat;
commit;
end $$
delimiter $$
create procedure insert_dept(in start int(10),in max_num int(10))
begin
declare i int default 0;
set autocommit = 0;
repeat
set i = i+1;
insert into dept(deptno,dname,loc) values((start+i),ran_string(10),ran_string(8));
until i=max_num
end repeat;
commit;
end $$
delimiter ;
call insert_dept(100,10);
delimiter ;
call insert_emp(100001,500000);
mysql提供的可以用来分析当前会话中语句执行的资源消耗情况。可以用来sql的调优的测量
默认情况下,参数处于关闭状态,并保存最近15次的运行结果
默认关闭的
SHOW VARIABLES LIKE 'profiling';
开启:
set profiling = on;
执行sql
select * from emp group by id limit 150000;
查看mysql历史执行语句:
SHOW PROFILES;
诊断sql
SHOW PROFILE CPU,BLOCK IO FOR QUERY 22;//22 是show profiles中的query id
常见问题:
converting HEAD to MyISAM查询结果太大,内存都不够用了往磁盘上搬。
Create tmp table 创建临时表 还要拷贝数据到临时表,用完删除临时表。
Copying to tmp table on disk 把内存中临时表复制到磁盘,危险
locked
开启
set global general_log=1;
set global log_output='TABLE';
查看结果:
select * from mysql.general_log;
锁是计算机协调多个进程或线程并发访问某一资源的机制
购物的时候,库存只有一件怎么解决?
读锁(共享锁):针对同一份数据,多个读操作可以同时进行二不会互相影响。
写锁(排它锁):当前写操作没有完成前,它会阻断其他写锁和读锁。
表锁
行锁
偏向MyISAM存储引擎,开销小,加锁快;无死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
建表
CREATE TABLE mylock (
id INT not NULL PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20)
)ENGINE MYISAM;
INSERT INTO mylock(NAME) VALUE('a');
INSERT INTO mylock(NAME) VALUE('b');
INSERT INTO mylock(NAME) VALUE('c');
INSERT INTO mylock(NAME) VALUE('d');
INSERT INTO mylock(NAME) VALUE('e');
手动添加表锁
lock table 表名 read(write),表名2 read(write),其他;
查看表上加过的锁
show open tables;
释放表锁
unlock tables;
添加读锁之后,
当前session可以查询表记录,其他session也可以查询该表记录
当前session不能查询其他没有锁定的表,其他session可以查询或者更新未锁定的表
当前session插入或者更新锁定的表都会提示错误,其他session插入或者更新会一直等待
当前session释放锁,其他session等待执行操作。
添加写锁之后,
当前session对锁定的表查询、更新、插入都可以执行,其他session对锁定表的操作阻塞
当前session释放锁之后,其他session继续操作
MyISAM在执行查询语句(select)前,会自动给涉及的所有表添加读锁,在执行增删改操作前,会自动给涉及的表添加写锁。
简而言之,就是读锁会阻塞写,但是不会阻塞读。而写锁会把读和写都阻塞。
show status like 'table%';
Table_locks_immediate:产生表级锁定的次数,表示可以立即获取锁的查询次数,每立即获取锁值加1;
Table_locks_waited: 出现表级锁定争用而发生等待的次数(不能立即获取锁的次数,每等待一次加1),此值高说明存在着严重的表级锁争用情况
MyISAM的读写锁调度是写优先,这也是myisam不适合做写为主表的引擎。因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞
偏向InnoDB存储引擎,开销大,加锁慢;会出现死锁;锁定力度最小,发生锁冲突的概率最低,并发度也最高。
查看mysql默认隔离级别
show variables like 'tx_isolation';
建表
create table test_innodb_lock( a int(11), b varchar(16) )engine=innodb;
insert into test_innodb_lock values(1, 'b2');
insert into test_innodb_lock values(3, '3');
insert into test_innodb_lock values(4, '4000');
insert into test_innodb_lock values(5, '5000');
insert into test_innodb_lock values(6, '6000');
insert into test_innodb_lock values(7, '7000');
insert into test_innodb_lock values(8, '8000');
insert into test_innodb_lock values(9, '9000');
insert into test_innodb_lock values(1, 'b1000');
create index test_innodb_a_ind on test_innodb_lock(a);
create index test_innodb_b_ind on test_innodb_lock(b);
案例
set autocommit=0;(两个session都)
当前session修改数据,然后select查看修改结果,其他session看到的是未修改的数据,读己之所写。
两个session操作相同的行,后面的阻塞,前面的提交后再执行。
当前窗口commit后,其他关闭autocommit的窗口要commit之后才能看见结果。
修改数据的时候索引失效(例如varchar不加单引号,导致索引失效),会导致行锁变表锁。
间隙锁
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,Innodb会给符合条件的已有数据记录的索引项添加锁;对于键值在条件范围内但并不存在的记录,也叫“间隙(GAP)”,Innodb也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。
当前session(关闭自动提交)
update test_innodb_lock set b='0629' where a>1 and a<6;
其他session
insert into test_innodb_lock values(2,'2000');
其他session 出现阻塞的情况
如何锁定一行
当前session
begin;
select * from test_innodb_lock where a=8 for update;
其他session
update test_innodb_lock set b='8001' where a=8;
其他session会出现阻塞
分析行锁
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 :系统启动后到现在总共等待的次数
特别注意:
Innodb_row_lock_time
Innodb_row_lock_time_avg
Innodb_row_lock_waits
优化建议
尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁
合理设计索引,尽量较小锁的范围
尽可能较少索引条件,避免间隙锁
尽量控制事务大小,减少锁定资源量和时间长度
尽可能低级别事务隔离
开销和加锁时间界于表锁和行锁之间,会出现死锁;锁定粒度界于行锁和表锁之间,并发度一般。
slave会从master读取binlog来进行数据同步
1. master将改变记录到二进制日志(binary log)。这些记录过程叫做二进制日志事件,binary log events;
2. slave将master的binary log events拷贝到它的中继日志(relay log);
3. slave重做中继日志中的事件,将改变应用到自己的数据库中。mysql复制是异步的且串行化的
每个salve只有一个master
每个slave只能有一个唯一的服务器ID
每个master可以有多个slave
延时
mysql版本一致且后台以服务运行
主从都配置在[mysqld]节点下,都是小写
修改主机my.cnf配置文件
# 唯一 必须
server-id=1
# 启用二进制日志 必须
log-bin=/var/lib/mysql/data/mysqlbin
# 启用错误日志 可选
log-err=/var/lib/mysql/data/mysqlerr
# 根路径 可选
basedir=/var/lib/mysql/
# 临时目录 可选
tmpdir=/var/lib/mysql/
# 数据目录 可选
datadir=/var/lib/mysql/
# 主机读写都可以
read-only=0
# 设置不要复制的数据库 可选
binlog-ignore-db=mysql
# 设置需要复制的数据库 可选
binlog-do-db=pzh
修改从机my.cnf配置文件
# 唯一 必须
server-id=1
# 启用二进制日志 必须
log-bin=/var/lib/mysql/data/mysqlbin
master
[mysqld]
datadir=/var/lib/mysql
basedir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
user=root
port=3306
symbolic-links=0
server-id=1
binlog-ignore-db=mysql
binlog-ignore-db=information_schema
binlog-ignore-db=performance_schema
binlog-ignore-db=sys
log-bin=mysql-bin
expire_logs_days=90
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
slave
[mysqld]
basedir=/var/lib/mysql
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
user=root
port=3306
server-id=2
log-bin=mysql-bin
skip_slave_start=1
read_only = 1
master_info_repository=TABLE
relay_log_info_repository=TABLE
symbolic-links=0
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
首先关闭防火墙
master:
grant replication slave on *.* to 'root'@'192.168.101.129' identified by 'root';
flush privileges;
show master status\G
slave:
grant replication slave on *.* to 'root'@'192.168.101.128' identified by 'root';
flush privileges;
change master to master_host='192.168.101.128', master_user='root', master_password='root',master_log_file='mysql-bin.000006', master_log_pos=306;
start slave;
show slave status\G