MySQL是一个关系型数据库管理系统,由瑞典MySQL AB公司开发,目前属于Oracle公司。
MySQL是一种关联数据库管理系统,将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。
Mysql是开源的,所以你不需要支付额外的费用。
Mysql是可以定制的,采用了GPL协议,你可以修改源码来开发自己的Mysql系统。
Mysql支持大型的数据库。可以处理拥有上千万条记录的大型数据库。
MySQL使用标准的SQL数据语言形式。Mysql可以允许于多个系统上,并且支持多种语言。这些编程语言包括C、C++、Python、Java、Perl、PHP、Eiffel、Ruby和Tcl等。MySQL支持大型数据库,支持5000万条记录的数据仓库,32位系统表文件最大可支持4GB,64位系统支持最大的表文件为8TB。
安装教程:
参考博客:
https://blog.csdn.net/weixin_42140801/article/details/107859925?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162167914316780255282076%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=162167914316780255282076&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-3-107859925.first_rank_v2_pc_rank_v29&utm_term=centos7+%E5%AE%89%E8%A3%85mysql&spm=1018.2226.3001.4187
安装过程中可能遇到的问题:
常用命令
启动服务
systemctl start mysqld.service
关闭服务
systemctl stop mysqld.service
登录客户端
mysql -u root -p
退出客户端
exit
查看MySQL进程
ps -ef|grep mysql
查看Linux系统运行状况,详情可自行查询
top
默认情况下MySQL的目录主要在四大文件目录中
登录客户端
查看字符集
show variables like '%char%';
如果 server 和 client 字符集是 Latin,会导致显示中文乱码,需要修改配置,修改配置需要重启数据库服务;
修改字符集之后,再创建的数据库才会生效,所以一开始安装MySQL时就要将字符集设置好
这里由于我们在上面安装的过程中,已经修改了配置文件,所以显示为utf8字符集
默认条件下,Windows环境配置文件叫my.ini,Linux环境下配置文件叫/etc/my.cnf
配置的内容主要包含:
和其它数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
连接层:最上层是一些客户端和连接服务,包含本地sock通信和大多数基于客户端/服务端工具实现的类似于tcp/ip的通信。
主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
服务层
引擎层:Pluggable Storage Engines ,可拔插组件式存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。主要介绍MyISAM和InnoDB
存储层:主要是将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互。
sql命令查看当前使用的存储引擎,两种命令
show engines;
show variables like '%storage_engine%';
对比项 | MyISAM | InnoDB |
---|---|---|
主外键 | 不支持 | 支持 |
事务 | 不支持 | 支持 |
行表锁 | 表锁,即使操作一条记录也会锁住整个行,不适合高并发操作 | 行锁,操作是只锁某一行,不对其他行影响,适合高并发操作 |
缓存 | 只缓存索引,不缓存真实数据 | 不仅缓存索引还缓存真实数据,对内存要求较高,而且内存大小对性能有决定性影响 |
表空间 | 小 | 大 |
关注点 | 性能,倾向读操作 | 事务 |
默认安装 | Y | Y |
补充知识:
Percona 为 MySQL 数据库服务器进行了改进,在功能和性能上较 MySQL 有着很显著的提升。该版本提升了在高负载情况下的 InnoDB 的性能、为 DBA 提供一些非常有用的性能诊断工具;另外有更多的参数和命令来控制服务器行为。
该公司新建了一款存储引擎叫xtradb完全可以替代innodb,且在性能和并发上做得更好
阿里巴巴大部分mysql数据库其实使用的percona的原型加以修改。AliSql+AliRedis
并不一开始就要优化sql,生产环境下需要复现出现的性能问题,且运行一段时间筛查定位,确认是sql问题,才开始优化
性能下降sql慢,执行时间长,等待时间长
手写:
select distinct 字段
from 表
left|right|inner join 表
on 关联条件
where 筛选条件
group by 分组
having 分组后筛选
order by 排序
limit 分页参数
机读:
from 表
on 关联条件
left|right|inner join 表
where 筛选条件
group by 分组
having 分组后筛选
select
distinct 去重
order by 排序
limit 分页参数
直接回答索引像一本书的目录,通过目录找到内容,这样显得不专业,很low
MySQL官方对索引的定义为:
索引的本质:
理解:
你可以简单理解为“排好序的快速查找数据结构”。
详解
二叉树、B TREE:将查询的数据与节点比较,小的往左边走,大的往右边走,到下一级继续比较查找,直至找到对应的值
二叉树弊端之一:二叉树很可能会发生两边不平衡的情况。B-TREE: (B:balance) 会自动根据两边的情况自动调节,使两端无限趋近于平衡状态。可以使性能最稳定。(myisam使用的方式) B-TREE弊端:(插入/修改操作多时,B-TREE会不断调整平衡,消耗性能)从侧面说明了索引不是越多越好。B+TREE:Innodb 所使用的索引
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上
我们平常所说的索引,如果没有特别指明,都是指B树(多路搜索树,并不一定是二叉的,比如三叉…)结构组织的索引。
其中聚集索引,次要索引,覆盖索引,复合索引,前缀索引,唯一索引默认都是使用B+树索引,统称索引。当然,除了B+树这种类型的索引之外,还有哈稀索引(hash index)等。
优势:
劣势:
单值索引:
唯一索引:
复合索引:
基本语法:
创建:
create [unique] index indexName on mytable(columnname(length))
ALTER mytable ADD [UNIQUE ] INDEX [indexName] ON (columnname(length))
两种创建方法,unique表示唯一索引,看需求添加;columnname(length)有一个就是单值索引,多个就是符合索引;indexname是索引名字,mytable是表名
删除
DROP INDEX [indexName] ON mytable;
查看
SHOW INDEX FROM table_name\G
使用ALTER有四种方式来添加数据表的索引:
BTree索引、Hash索引、full-text全文索引、R-Tree索引;我们重点探讨BTree索引
BTree索引
索引的高度越高,查找的层次越多,IO消耗越多,所以我们应该限制索引的高度,增加索引的广度
一颗b树,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1、P2、P3,P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。非叶子节点不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。
【查找过程】
如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。
真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。
哪些情况需要建索引:
哪些情况不需要创建索引:
MySQL中有专门负责优化select语句的优化器模块,主要功能:通过计算分析系统中搜集到的统计信息,为客户端请求的Query提供他认为最优的执行计划,但他认为最优的数据检索方式,不见得是DBA认为最优的,这部分最耗费时间
当客户端向MySQL请求一条Query,命令解析器模块完成请求分类,区分出是select并发给MySQL Query Optimizer时,MySQL Query Optimizer首先会对整条Query进行优化,处理掉一些常量表达式的预算,直接换成常量值。并对Query中的查询条件进行简化和转换,去掉一些无用或显而易见的条件、结构调整等。然后分析Query中的Hint信息(如果有),看显示Hint信息是否可以完全确定该Query的执行计划。如果没有Hint或者Hint信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据Query进行写相应的计算分析,然后再得出最后的执行计划。
CPU:
IO:
服务器硬件的性能瓶颈:
锁:
是什么
查询执行计划
使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是
如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈
官网介绍:https://dev.mysql.com/doc/refman/8.0/en/explain-output.html
能干什么
怎么用
id
表示select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
如果id相同,则执行/加载顺序由上至下
三个表都是1,那么执行顺序按照自上而下执行,t1,t3,t2
如果id不同,且查询为子查询,则id的序号会递增,id值越大优先级越高,越先被执行
查询顺序为 t3,t1,t2,结合我们的理解,查询是按照从里到外的顺序进行的
id同时有相同和不同的,先执行数值大的,相同的值可以看做一组,组内按照从上到下
执行顺序为 t3,derived2,t2,derived2是id=2的查询衍生出的虚表s1,
select_type
查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询
SIMPLE:
PRIMARY:
DERIVED:
SUBQUERY:
DEPENDENT SUBQUERY:
UNCACHEABLE SUBQUREY:
UNION:
UNION RESULT:
table
显示这一行的数据是关于哪张表的
type
从最好到最差依次是:system > const > eq_ref > ref > range > index >ALL
一般来说,得保证查询至少达到range级别,最好能达到ref。
system:系统
const:常量
如将主键置于where列表中,MySQL就能将该查询转换为一个常量
eq_ref:唯一索引扫描
比如,部门表每个部门都有id,部门里有员工,总裁办部门只有一个CEO,给部门id加索引;
当我们查询总裁办的员工时,只有一个记录CEO。
ref:非唯一索引扫描
比如,我们根据研发部id查询查询Java开发工程师,结果会有很多行
range:范围
possible_keys
key
索引没有使用到,就是索引失效
比如,可能有两个,实际只有一个
理论上没有用索引,实际上没有用索引
理论上为null,实际上是index
key_len
精确性和key_len是相互矛盾的,越精确就越需要更多的索引定位,所以,我们要平衡利弊,尽量提高精确,降低使用的索引长度
可以看得见,同样的查询结果,第一个查询用的条件少,key_len小,第二个条件多,key_len大;但是结果一样,所以第一个查询更优
ref
这句话的含义:
type字段为使用的索引类型,当type字段为const、eq_ref、ref、ref or null、unique_subquery、index_subquery 的其中之一时,ref列展示的就是与索引列做等值匹配的条件,比如常数或某个列
上图,t2表没用索引,忽略掉;shared是库名,不要被误导
t1表的type为ref(非唯一索引扫描),因为where条件中有t1表的col1、col2两个字段,所以 idx_col1_col2 这个索引被使用,
其中,col1字段来源于t2.col1,col2字段来源于常量’ac’,所以ref列中显示的就是 shared.t2.col1和const
总结就是:如果type列显示使用了哪种索引,那么ref列就会显示这个被使用的索引锁匹配了哪个值
rows
第一次查询时,没有创建索引;t2表全表搜索,t1表使用了主键索引,共计检索641行
第二次查询,由于创建了idx_col1_col2复合索引,t2表使用了复合索引,t1表使用了主键索引,共计检索199行
Extra
包含不适合在其他列中显示但十分重要的额外信息
Using filesort、Using temporary、Using index 是重点
Using filesort :
出现这种情况比较坑爹,有索引没用上,自己另起炉灶重新排序,耗费时间、资源;要尽可能避免这种情况
第一个查询:
使用的索引是 col1_col2_col3,但我们使用的条件和排序字段从左到右是col1 col3,与索引并不完全匹配,导致extra中出现了,using filesort,这样导致索引没有充分利用,使用了文件排序,效率低下
第二个查询:
使用的索引是col1_col2_col3,使用的条件和排序字段从左到右是 col1 col2 col3,与索引匹配,索引完全起作用,extr中提示 using index,查询效率高于第一个
Using temporary
这种现象更严重,条件和排序使用了临时表,更加消耗资源,拖慢速度
第一个查询:
使用到的索引字段为 col2,与复合索引不匹配,所以出现了 using filesort,且产生了临时表,using temporary
第二个查询:
排序使用的索引为 col1 col2,与复合索引匹配,效率高
USING index
覆盖索引(Covering Index):
理解方式一:
就是select的数据列只用从索引中就能取得,不必读取数据行,MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,换句话说,查询列要被所建的索引覆盖
理解方式二:
索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含了或覆盖了满足查询结果的数据就叫做覆盖索引
注意:
如果要使用覆盖索引,一定要注意select列表中只取出需要的列,不可用select * 。因为将所有字段一起做索引会导致索引文件过大,查询性能下降。
Using where
表明使用了where过滤
using join buffer
使用了连接缓存
impossible where
where子句的值总是false,不能用来获取任何元素
select tables optimized away
distinct
优化distinct操作,在找到第一匹配的元素后即停止找同样值的动作,
单表
建表sql
CREATE TABLE IF NOT EXISTS `article` (
`id` INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
`author_id` INT(10) UNSIGNED NOT NULL,
`category_id` INT(10) UNSIGNED NOT NULL,
`views` INT(10) UNSIGNED NOT NULL,
`comments` INT(10) UNSIGNED NOT NULL,
`title` VARBINARY(255) NOT NULL,
`content` TEXT NOT NULL);
INSERT INTO `article`(`author_id`, `category_id`, `views`, `comments`, `title`, `content`) VALUES
(1, 1, 1, 1, '1', '1'),
(2, 2, 2, 2, '2', '2'),
(1, 1, 3, 3, '3', '3');
SELECT * FROM article;
先实现功能,此时并没有创建索引
EXPLAIN SELECT id,author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
结论:显然, type为all,最坏的情况,extra中有using filesort也是最坏的情况,必须优化!
先创建个 category_id,comments,views的复合索引,
ALTER TABLE `article` ADD INDEX idx_article_ccv ( `category_id` , `comments`, `views` );
create index idx_article_ccv on article(category_id,comments,views);
再次查询分析
type问题解决了,但是extra依然有 using filesort,为什么?
因为按照BTREE索引工作原理,先排序 category_id,如果遇到相同的 category_id,则再排序comments,如果遇到相同的comments则再排序views
当comments字段在联合索引中处于中间位置时,comments>1是一个范围条件range,MySQL无法利用索引再对后面的views部分进行检索,即range类型查询字段后面的索引无效;
所以要尽量避免出现范围的条件
当前索引不是最优,删除掉这个索引,
DROP INDEX idx_article_ccv ON article;
再次建索引,将comments剔除掉
ALTER TABLE `article` ADD INDEX idx_article_cv ( `category_id` , `views` ) ;
create index idx_article_cv on article(category_id,views);
查询分析
结论:可以看到,type 变为了 ref,Extra 中的 Using filesort 也消失了,结果非常理想。
两表
建表
CREATE TABLE IF NOT EXISTS `class` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`id`));
CREATE TABLE IF NOT EXISTS `book` (
`bookid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`bookid`));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO class(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO book(card) VALUES(FLOOR(1 + (RAND() * 20)));
可以理解为员工表和部门表
开始explain分析
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
结论:type 有All
添加索引优化,左表还是右表不知道,先建一个看看,
ALTER TABLE `book` ADD INDEX Y ( `card`);
第2次explain
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
可以看到第二行的 type 变为了 ref,rows 也变成了优化比较明显。这是由左连接特性决定的。LEFT JOIN 条件用于确定如何从右表搜索行,左边一定都有,所以右边是我们的关键点,一定需要建立索引。
如果把索引换个位置呢?将索引加载左连接的左表上
删除旧索引 + 新建 + 第3次explain
DROP INDEX Y ON book;
ALTER TABLE class ADD INDEX X (card);
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
结论:左连接加右表,右连接加左表
三表
建表,加上之前的两表,一共三表
CREATE TABLE IF NOT EXISTS `phone` (
`phoneid` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`card` INT(10) UNSIGNED NOT NULL,
PRIMARY KEY (`phoneid`))ENGINE=INNODB;
INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
INSERT INTO phone(card) VALUES(FLOOR(1 + (RAND() * 20)));
查询分析
EXPLAIN SELECT * FROM class INNER JOIN book ON class.card=book.card INNER JOIN phone ON book.card=phone.card;
因为连接较多,使用了 usring join buffer连接缓存;全部都是全表扫描
创建索引
ALTER TABLE `phone` ADD INDEX z(`card`);
ALTER TABLE `book` ADD INDEX Y(`card`);
查询分析
后两行的 type 都是 ref ,总rows优化效果不错,因此索引最好设置在需要经常查询的字段中
结论:join的优化
建表
CREATE TABLE staffs (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR (24) NULL DEFAULT '' COMMENT '姓名',
age INT NOT NULL DEFAULT 0 COMMENT '年龄',
pos VARCHAR (20) NOT NULL DEFAULT '' COMMENT '职位',
add_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间') CHARSET utf8 COMMENT '员工记录表' ;
INSERT INTO staffs(NAME,age,pos,add_time) VALUES('z3',22,'manager',NOW());
INSERT INTO staffs(NAME,age,pos,add_time) VALUES('July',23,'dev',NOW());
INSERT INTO staffs(NAME,age,pos,add_time) VALUES('2000',23,'dev',NOW());
INSERT INTO staffs(NAME,age,pos,add_time) VALUES(null,23,'dev',NOW());
SELECT * FROM staffs;
ALTER TABLE staffs ADD INDEX idx_staffs_nameAgePos(name, age, pos);
1. 全值匹配我最爱
索引 idx_staffs_nameAgePos 建立索引时 以 name , age ,pos 的顺序建立的。
全值匹配表示 按顺序匹配的
EXPLAIN SELECT * FROM staffs WHERE NAME = 'July';
EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND age = 25;
EXPLAIN SELECT * FROM staffs WHERE NAME = 'July' AND age = 25 AND pos = 'dev';
如果我们改变sql语句中的条件的顺序或个数
2. 最佳左前缀法则
如果索引了多列,要遵守最左前缀法则;指的是查询从索引的最左前列开始,并且不跳过中间的索引列。
比如说,我们创建的索引是 name , age ,pos 的顺序,那么我们查找的使用的条件、排序也要按照 name , age ,pos 的顺序
如果查询条件的哪一处位置与索引顺序不一致,之前的索引字段有效,之后的索引字段无效;
这也是只有第一个字段索引生效,因为从第二个开始字段开始就断开了,不一致
3. 请不要在索引列上做任何的操作(计算、函数、自动or手动类型转化),会导致索引失效,全表扫描
4. 存储引擎不能使用索引中的范围条件右边的列
第一句正常使用,索引生效
第二句,name 用到,age 用到,但是age > 25 是范围,所以age 之后(右侧)的索引失效
5. 尽量使用覆盖索引,只访问索引的查询,即索引列和查询列一致,减少select星
按需取字段,尽量和索引重合
虽然两句都是用到了索引,但是下边的多了using index,使用了覆盖索引,效果更好
6. mysql 在使用不等于(!= 或者<>)的时候无法使用索引会导致全表扫描
7. is not null 也无法使用索引,但是 is null 是可以使用索引的
8. like以通配符开头(’%abc…’)mysql索引失效会变成全表扫描的操作
三个语句只有 like 后面的%不同,其他相同;左边加%会导致索引失效,加在右边不会
如何解决like ‘%字符串%’ 时的索引失效问题?
select 字段 的时候,直接将查询的字段满足覆盖索引,
9. 字符串不加单引号会导致索引失效
10. 少用or,用它连接会导致索引失效
口诀:
思考:为什么索引要满足最佳左前缀匹配呢?
因为根据因为按照BTree索引工作原理,在进行条件筛选和排序的时候,是按照从左到右的顺序逐级查找的;
也就是,按照条件和排序字段,从左到右,先排序第一个字段,出现相同的字段,再排序第二个字段,如果遇到相同的字段,再排序第三个字段,以此类推;
而索引发挥作用也按照上面从左到右的顺序
所以当我们创建索引也遵循这样的顺序时,查找字段的先后顺序刚好与索引的结构顺序一致,索引可以发挥作用
注意:
分析:
分析:
小结:
分析:
类似嵌套循环
in 与 exists 使用效果相似,但是性能有所差异;具体看关键字两侧的大小关系
select ... from table where exists(subquery)
该语法可以理解为,将主查询的数据,放到子查询中做条件验证,根据验证结果(true或false)来决定主查询的数据结果是否得以保留
提示:
尽量使用Index方式排序,避免使用FileSort方式排序
建表
CREATE TABLE tblA(
id int primary key not null auto_increment,
age INT,
birth TIMESTAMP NOT NULL,
name varchar(200));
INSERT INTO tblA(age,birth,name) VALUES(22,NOW(),'abc');
INSERT INTO tblA(age,birth,name) VALUES(23,NOW(),'bcd');
INSERT INTO tblA(age,birth,name) VALUES(24,NOW(),'def');
CREATE INDEX idx_A_ageBirth ON tblA(age,birth,name);
SELECT * FROM tblA;
我们的优化重点,就是不要出现filesort
分析:
MySQL支持两种方式的排序,filesort和index。index效率高,它指MySQL扫描索引本身完成排序;filesort方式效率低。
order by满足两种情况,会使用index方式排序
尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀
如果不在索引列上,filesort有两种算法:
mysql就要启动双路排序和单路排序
双路排序
单路排序:
结论及引申出的问题:
优化策略:
提高Order By的速度:
几乎和order by 一致,但也有区别
优化策略:
MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。
具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为10,意思是运行10秒以上的语句。
由他来查看哪些SQL超出了我们的最大忍耐时间值,比如一条sql执行超过5秒钟,我们就算慢SQL,希望能收集超过5秒的sql,结合之前explain进行全面分析。
说明:
默认情况下,MySQL数据库没有开启慢查询日志,需要我们手动来设置这个参数。当然,
如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件
查看是否开启及如何开启
默认
SHOW VARIABLES LIKE '%slow_query_log%';
默认情况下slow_query_log的值为OFF,表示慢查询日志是禁用的,可以通过设置slow_query_log的值来开启SHOW VARIABLES LIKE ‘%slow_query_log%’;
开启
set global slow_query_log=1;
使用set global slow_query_log=1开启了慢查询日志只对当前数据库生效,如果MySQL重启后则会失效。
全局变量设置,对当前连接不影响,对当前连接立刻生效;
如果要永久生效,就必须修改配置文件my.cnf(其它系统变量也是如此)
修改my.cnf文件,[mysqld]下增加或修改参数slow_query_log 和slow_query_log_file后,然后重启MySQL服务器。也即将如下两行配置进my.cnf文件
slow_query_log =1
slow_query_log_file=/var/lib/mysql/atguigu-slow.log
关于慢查询的参数slow_query_log_file ,它指定慢查询日志文件的存放路径,系统默认会给一个缺省的文件host_name-slow.log(如果没有指定参数slow_query_log_file的话)
那么开启了慢查询日志后,什么样的SQL才会记录到慢查询日志里面呢?
这个是由参数long_query_time控制,默认情况下long_query_time的值为10秒,命令:
SHOW VARIABLES LIKE 'long_query_time%';
可以使用命令修改,也可以在my.cnf参数里面修改。假如运行时间正好等于long_query_time的情况,并不会被记录下来。也就是说,在mysql源码里是判断大于long_query_time,而非大于等于。
case
查看多少秒算慢
SHOW VARIABLES LIKE 'long_query_time%';
设置慢的阈值时间
使用命令 set global long_query_time=1修改为阙值到1秒钟的就是慢sql 修改后发现long_query_time并没有改变。
为什么设置后看不出变化?
需要重新连接或新开一个会话才能看到修改值。
SHOW VARIABLES LIKE 'long_query_time%';
或者通过set session long_query_time=1来改变当前session变量;
记录慢SQL并后续分析; 实验一条慢sql跟踪日志信息
查询当前系统中有多少条慢查询记录
show global status like '%Slow_queries%';
配置版
【mysqld】下配置:
slow_query_log=1;
slow_query_log_file=/var/lib/mysql/atguigu-slow.log
long_query_time=3;
log_output=FILE
在生产环境中,如果要手动分析日志、查找、分析SQL,显然是个体力活,MySQL提供了日志分析工具
查看mysqldumpslow的帮助信息
mysqldumpslow --help
工作常用参考
得到返回记录集最多的10个
得到访问次数最多的10个
得到按照时间排序的前10条里面含有左连接的查询语句
另外建议在使用这些命令时结合 | 和more 使用 ,否则有可能出现爆屏情况
往表里插入1000W数据
渐变
# 新建库
create database bigData;
use bigData;
#1 建表dept
CREATE TABLE dept(
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,
dname VARCHAR(20) NOT NULL DEFAULT "",
loc VARCHAR(13) NOT NULL DEFAULT "" ) ENGINE=INNODB DEFAULT CHARSET=UTF8 ;
#2 建表emp
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 ;
设置参数log_bin_trust_function_creators
创建函数,假如报错:
This function has none of DETERMINISTIC…#
由于开启过慢查询日志,因为我们开启了 bin-log, 我们就必须为我们的function指定一个参数。
show variables like 'log_bin_trust_function_creators';
set global log_bin_trust_function_creators=1;
这样添加了参数以后,如果mysqld重启,上述参数又会消失,永久方法:
windows下my.ini[mysqld]加上
log_bin_trust_function_creators=1
linux下 /etc/my.cnf下my.cnf[mysqld]加上
log_bin_trust_function_creators=1
是什么:
官网:
默认情况下,参数处于关闭状态,并保存最近15次的运行结果
是否支持,看看当前的mysql版本是否支持
Show variables like 'profiling';
开启功能,默认是关闭,使用前需要开启
show variables like 'profiling';
set profiling=1;
运行SQL
select * from emp group by id%10 limit 150000;
select * from emp group by id%20 order by 5
查看结果,
show profiles;
诊断SQL,show profile cpu,block io for query n (n为上一步前面的问题SQL数字号码);
show profile cpu,block io for query n;
最常用的就是CPU和IO信息,此外我们还可以修改参数查看其他信息
# show profile type for query queryID
type:
| ALL -显示所有的开销信息
| BLOCK I0 -显示块IO相关开销
| CONTEXT SWITCHES -上下文切换相关开销
| CPU -显示CPU相关开销信息
| IPC -显示发送和接收相关开销信息
| MEMORY -显示内存相关开销信息
| PAGE FAULTS -显示页面错误相关开销信息
| SOURCE -显示和Source_function,Source_file,Source_line相关的开销信息
| SWAPS - 显示交换次数相关开销的信息
如何从查询的信息分出问题呢?
出现以下信息,说明sql执行存在明显问题:
永远不要在生产环境开启这个功能,测试时使用
配置启用
在mysql的my.cnf中,设置如下:
#开启
general_log=1
# 记录日志文件的路径
general_log_file=/path/logfile
#输出格式
log_output=FILE
编码启用
set global general_log=1;
#全局日志可以存放到日志文件中,也可以存放到Mysql系统表中。存放到日志中性能更好一些,存储到表中
set global log_output='TABLE';
#此后 ,你所编写的sql语句,将会记录到mysql库里的general_log表,可以用下面的命令查看
select * from mysql.general_log;
相比之下,show profile 更好用一些
锁是计算机协调多个进程或线程并发访问某一资源的机制
在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。
打个比方,我们到淘宝上买一件商品,商品只有一件库存,这个时候如果还有另一个人买,那么如何解决是你买到还是另一个人买到的问题? 这里肯定要用到事务,我们先从库存表中取出物品数量,然后插入订单,付款后插入付款表信息,然后更新商品数量。在这个过程中,使用锁可以对有限的资源进行保护,解决隔离和并发的矛盾。
从对数据操作的类型(读\写)分
从对数据操作的粒度分
为了尽可能提高数据库的并发度,每次锁定的数据范围越小越好,理论上每次只锁定当前操作的数据的方案会得到最大的并发度,但是管理锁是很耗资源的事情(涉及获取,检查,释放锁等动作),因此数据库系统需要在高并发响应和系统性能两方面进行平衡,这样就产生了“锁粒度(Lock granularity)”的概念。一种提高共享资源并发发性的方式是让锁定对象更有选择性。尽量只锁定需要修改的部分数据,而不是所有的资源。更理想的方式是,只对会修改的数据片进行精确的锁定。任何时候,在给定的资源上,锁定的数据量越少,则系统的并发程度越高,只要相互之间不发生冲突即可。
特点:
偏向MyISAM存储引擎,开销小,加锁快;无死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
案例分析
建表
【表级锁分析--建表SQL】
create table mylock( id int not null primary key auto_increment, name varchar(20))engine myisam;
insert into mylock(name) values('a');
insert into mylock(name) values('b');
insert into mylock(name) values('c');
insert into mylock(name) values('d');
insert into mylock(name) values('e');
select * from mylock;
【手动增加表锁】
lock table 表名字1 read(write),表名字2 read(write),其它;
【查看表上加过的锁】
show open tables;
【释放表锁】
unlock tables;
session1也不能修改mylock表,如果执行写操作,则会报错
session1不能读其他表,是因为锁表是比较重要的事情,MySQL要求你必须把当前锁表的事情解决完,解锁,再去做其他事情;
session2可以读mylock,不能写mylock,如果执行写操作,则会出现阻塞的情况;此时若mylock解锁,session2就解除阻塞,继续执行阻塞前的命令;
加写锁
lock table mylock write;
session1自己可以读自己的写锁表,修改自己的写锁表,但是不能读其他表,因为自己当前的事情还没处理完,解锁之后才可以
session2读mylock也会发生阻塞,直到session1解锁,session2才会执行完之前的语句;写操作session2更没机会了
小结
MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁。
MySQL的表级锁有两种模式:
锁类型 | 可否兼容 | 读锁 | 写锁 |
---|---|---|---|
读锁 | 是 | 是 | 否 |
写锁 | 是 | 否 | 否 |
结合上表,所以对MyISAM表进行操作,会有以下情况:
简而言之:读锁会阻塞写,但是不会阻塞读;写锁会把读和写都阻塞
表锁分析
查看哪些表被加锁了
show open tables;
如何分析表锁定,
可以通过检查 table_locks_waited 和 table_locks_immediate 状态变量来分析系统上的表锁定
show status like 'table%';
table_locks_waited
此外,Myisam引擎的读写锁调度是写优先,这也是Myisam不适合做写为主表的引擎,因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞
特点
InnoDB与MyISAM的最大不同有两点:
并发事务带来的问题
更新丢失(Lost Update):
当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题--最后的更新覆盖了由其他事务所做的更新。
例如,两个程序员修改同一java文件。每程序员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。最后保存其更改副本的编辑人员覆盖前一个程序员所做的更改。
如果在一个程序员完成并提交事务之前,另一个程序员不能访问同一文件,则可避免此问题。
脏读(Dirty Reads):
不可重复读(Non-Repeatable Reads):
幻读(Phantom Reads):
事务的隔离级别
脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。
数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上 “串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。
常看当前数据库的事务隔离级别:
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,'b1');
create index test_innodb_a_ind on test_innodb_lock(a);
create index test_innodb_lock_b_ind on test_innodb_lock(b);
select * from test_innodb_lock;
行锁基本演示
由于MySQL每一句执行都会自动提交事务,我们为了演示,暂时将1和2的自动提交关闭,改为手动提交事务
set autocommit=0;
session1锁行之后,session2无法操作当前行,会产生阻塞;
当session1提交事务,session2也要提交事务(因为session2设置了手动提交),再查询才能看到当前行的最新数据
无索引导致行锁升级为表锁
现在innodb默认是行锁
如果此时,我们创建索引,字段a 和 字段b,分别为数值和字符
session1和session2各自改不同的行,各自提交,不会出现任何问题
但是,如果session1修改行时,没有给b字段加单引号,虽然可以隐式转换,但是会导致 b 索引失效,引发session1从锁行变成锁表;
session2发生阻塞,无法操作表;直到session1提交事务,session2才会解除阻塞;
因此,varchar类型如果不加单引号,极有可能导致行锁变表锁,发生阻塞;且这种现象很隐蔽,不容易发现;
间隙锁的危害
当我们用范围条件,而不是相等条件检索数据的时候,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙”
InnoDB也会对这个间隙加锁,这种锁的机制,就是所谓的间隙锁(Next-Key锁)
因为Query执行过程中,通过范围查找的话,他会锁整个范围内所有的索引键值,即使这个键值不存在;
间隙锁有一个比较致命的弱点,就是当锁定一个范围键之后,即使某些不存在的键值也会被无辜的锁定,造成在锁定期间无法插入锁定范围内的任何数据,在某些场景下可能会对性能造成很大的伤害
举例:
当前表中,a字段没有2这个值;
如果此时session1执行sql,条件限定为a的范围;那么在session1提交之前,范围内的行都会被锁
如果此时session2提交一条数据,且 a=2,尽管这是一条新数据,不该被session1锁住,但a=2在session1的锁表范围之内,所以session2发生阻塞;直到session1提交事务,session2才正常执行
如何锁定一行
行锁小结
InnoDB存储引擎由于实现了行锁定,虽然在锁定机制的实现上带来性能损耗可能比表锁高一些,但在整体并发处理能力方面远远优于Myisam的表锁定。当系统并发量高的时候,InnoDB的整体性能和Myisam相比就会有比较明显的优势
但是InnoDb的行锁同样也有弱点,如果使用不当,会让InnoDB的整体性能表现不如Myisam,甚至还会更差
如何分析行锁定
通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况
show status like 'innodb_row_lock%';
显示的状态量含义如下:
其中,平均时长、总次数、总时长 最重要
尤其是等待次数很高,且每次等待时间也比较大时,我们需要分析系统出现了什么问题,开始优化
优化建议
开销和加锁时间界于表锁和行锁之间;
会出现死锁;
锁定粒度界于表锁和行锁之间,并发度一般。
slave会从master读取binlog(二进制日志文件)来进行数据同步
三步骤+原理图
延时
这里我们以windows作为主机,Linux作为从机
mysql版本一致且后台以服务运行
确保主从服务器在同一网段内,ping测试可以联通,双边都ping一下测试
主从都配置在[mysqld]结点下,都是小写
主机修改my.ini配置文件
从机修改my.cnf配置文件