MySQL深入学习--配置索引执行计划

Posted LIFE

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL深入学习--配置索引执行计划相关的知识,希望对你有一定的参考价值。

mysql

一、MySQL 5.7 初始化配置

1.初始化数据并配置

# 1.初始化数据
/usr/local/mysql/bin/mysqld --initialize-insecure  --user=mysql --datadir=/opt/mysql/data --basedir=/opt/mysql

# 2.配置文件
vim /etc/my.cnf
[mysqld]
basedir=/usr/local/mysql
datadir=/usr/local/mysql/mydata
socket=/tmp/mysql.sock
log_error=/var/log/mysql.log
user=mysql
port=6606
[mysql]
socket=/tmp/mysql.sock


# 作用:
# 1.影响服务端的启动
# 标签: [mysqld]   [mysqld_safe]  [server] ...
[mysqld]
basedir=/opt/mysql          # 指定Mysql安装的绝对路径  
datadir=/opt/mysql/data		# Mysql数据存放的绝对路径
user=mysql					# 默认使用mysql来启动mysqld程序。
socket=/tmp/mysql.sock		# 套接字文件
port=3306 					# 指定了Mysql开放的端口
server_id=6

# 2.影响客户端连接
# 标签: [client]   [mysql]  [mysqldump] ....
[mysql] 
socket=/tmp/mysql.sock		# 套接字文件

2.多实例(3307 3308 3309)

# 1.创建相关目录
mkdir -p /data/330{7..9}/data 

# 2.创建配置文件
cat>> /data/3307/my.cnf<<EOF
[mysqld]
basedir=/opt/mysql              
datadir=/data/3307/data
user=mysql
socket=/data/3307/mysql.sock
port=3307 
server_id=3307
EOF

cp /data/3307/my.cnf /data/3308 
cp /data/3307/my.cnf /data/3309 

sed -i \'s#3307#3308#g\' /data/3308/my.cnf 
sed -i \'s#3307#3309#g\' /data/3309/my.cnf 


# 3.初始化数据 
mysqld --initialize-insecure  --user=mysql --datadir=/data/3307/data --basedir=/opt/mysql
mysqld --initialize-insecure  --user=mysql --datadir=/data/3308/data --basedir=/opt/mysql
mysqld --initialize-insecure  --user=mysql --datadir=/data/3309/data --basedir=/opt/mysql


# 4.启动多实例
chown -R mysql.mysql /data/*
mysqld_safe --defaults-file=/data/3307/my.cnf &
mysqld_safe --defaults-file=/data/3308/my.cnf &
mysqld_safe --defaults-file=/data/3309/my.cnf &

 
# 5.测试 
# 查看端口
netstat -lnp|grep 330
# 测试是否可以使用
mysql -S /data/3307/mysql.sock
mysql -S /data/3308/mysql.sock
mysql -S /data/3309/mysql.sock

# 6.systemd管理多实例
# 配置
cat >> /etc/systemd/system/mysqld3307.service <<EOF
[Unit]
Description=MySQL Server
Documentation=man:mysqld(8)
Documentation=http://dev.mysql.com/doc/refman/en/using-systemd.html
After=network.target
After=syslog.target
[Install]
WantedBy=multi-user.target
[Service]
User=mysql
Group=mysql
ExecStart=/opt/mysql/bin/mysqld --defaults-file=/data/3307/my.cnf
LimitNOFILE = 5000
EOF

# 把配置好的3307的配置文件,复制给3308、3309
cp  /etc/systemd/system/mysqld3307.service   /etc/systemd/system/mysqld3308.service 
cp  /etc/systemd/system/mysqld3307.service   

/etc/systemd/system/mysqld3309.service 
sed -i \'s#3307#3308#g\'   /etc/systemd/system/mysqld3308.service
sed -i \'s#3307#3309#g\'   /etc/systemd/system/mysqld3309.service


# 以后可以用 systemctl 来启动
systemctl start mysqld3307
systemctl start mysqld3308
systemctl start mysqld3309

# 设置开机启动
systemctl enable  mysqld3307
systemctl enable  mysqld3308
systemctl enable  mysqld3309

二、MySQL密码

# 设置root用户密码123
mysqladmin -uroot -p password 123

# 可以去用户表里查看用户名,密码,HOST
select user,authentication_string,host from mysql.user;



# 忘记密码
# 1.停止数据库
/etc/init.d/mysqld stop

# 2.启动数据库为无密码验证模式
mysqld_safe --skip-grant-tables --skip-networking  &

# 3.修改密码
update mysql.user set authentication_string=PASSWORD(\'456\') where user=\'root\' and host=\'localhost\';

# 4.重启MySQL服务
/etc/init.d/mysqld restart


# 5.测试
mysql -uroot -p123
mysql -uroot -p456

三、数据类型和字符集

	整型
	int 最多存10位数字
		-2^31 ~ 2^31-1
          2^32  10位数    11 
    浮点
	
	字符串类型
		char      定长,存储数据效率较高,对于变化较多的字段,空间浪费较多
		varchar   变长,存储时判断长度,存储会有额外开销,按需分配存储空间.
		enum
	时间
		datetime  
		timestamp
		date
		time	

# SQL语句规范第五条:	
	1.少于10位的数字int ,大于10位数 char,例如手机号
	2.char和varchar选择时,字符长度一定不变的可以使用char,可变的尽量使用varchar
	  在可变长度的存储时,将来使用不同的数据类型,对于索引树的高度是有影响的.
    3.选择合适的数据类型
	4.合适长度

四、索引

1.索引

索引就是表中的某个列

作用: 优化查询

select 查询有三种情况:缓存查询(不在mysql中进行数据查询),全表扫描,索引扫描

2.索引分类

  • Btree(btree b+tree b*tree)

    即二叉搜索树:

    ​ 1.所有非叶子结点至多拥有两个儿子(Left和Right);

    ​ 2.所有结点存储一个关键字;

    ​ 3.非叶子结点的左指针指向小于其关键字的子树,右指针指向大于其关键字的子树;

  • Rtree

  • HASH

  • FullText

3.btree

B-Tree是为磁盘等外存储设备设计的一种平衡查找树。因此在讲B-Tree之前先了解下磁盘的相关知识。

系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。

InnoDB存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位。InnoDB存储引擎中默认每个页的大小为16KB

而系统一个磁盘块的存储空间往往没有这么大,因此InnoDB每次申请磁盘空间时都会是若干地址连续磁盘块来达到页的大小16KB。InnoDB在把磁盘数据读入到磁盘时会以页为基本单位,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘I/O次数,提高查询效率。

B-Tree结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述B-Tree,首先定义一条记录为一个二元组[key, data] ,key为记录的键值,对应表中的主键值,data为一行记录中除主键外的数据。对于不同的记录,key值互不相同。

一棵m阶的B-Tree有如下特性:

  1. 每个节点最多有m个孩子。
  2. 除了根节点和叶子节点外,其它每个节点至少有Ceil(m/2)个孩子。
  3. 若根节点不是叶子节点,则至少有2个孩子
  4. 所有叶子节点都在同一层,且不包含其它关键字信息
  5. 每个非终端节点包含n个关键字信息(P0,P1,…Pn, k1,…kn)
  6. 关键字的个数n满足:ceil(m/2)-1 <= n <= m-1
  7. ki(i=1,…n)为关键字,且关键字升序排序。
  8. Pi(i=1,…n)为指向子树根节点的指针。P(i-1)指向的子树的所有节点关键字均小于ki,但都大于k(i-1)

B-Tree中的每个节点根据实际情况可以包含大量的关键字信息和分支,如下图所示为一个3阶的B-Tree:

每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。以根节点为例,关键字为17和35,P1指针指向的子树的数据范围为小于17,P2指针指向的子树的数据范围为17~35,P3指针指向的子树的数据范围为大于35。

模拟查找关键字29的过程:

  1. 根据根节点找到磁盘块1,把磁盘块1读入内存。【磁盘I/O操作第1次】
  2. 比较关键字29在区间(17,35),找到磁盘块1的指针P2。
  3. 根据P2指针找到磁盘块3,把磁盘块3读入内存。【磁盘I/O操作第2次】
  4. 比较关键字29在区间(26,30),找到磁盘块3的指针P2。
  5. 根据P2指针找到磁盘块8,把磁盘块8读入内存。【磁盘I/O操作第3次】
  6. 在磁盘块8中的关键字列表中找到关键字29。

从这看来,每次查找一个数,只需要经过三步即可,大大的提高了查找效率

缺点:

1.每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点(即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。

2.每次查询需要从根节点开始查询,如果执行次数过多,效率一样不高

4.B+Tree

B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构,InnoDB存储引擎就是用B+Tree实现其索引结构。

# B+Tree相对于B-Tree有几点不同:

1. 非叶子节点只存储键值信息。
2. 所有叶子节点之间都有一个链指针。
3. 数据记录都存放在叶子节点中。
# B+Tree的优点:

1. 由于相邻的叶子节点之间有链接,所以查询相邻的数据时,不用再次从头开始执行,执行效率高
2. 因为非叶子节点只存储键值信息,并且数据记录都存放在叶子节点中,所以大大提高每个节点存储的key值数量,降低B+Tree的高度。

将上一节中的B-Tree优化,由于B+Tree的非叶子节点只存储键值信息,假设每个磁盘块能存储4个键值及指针信息,则变成B+Tree后其结构如下图所示:

5.Btree 分类

数据库中的B+Tree索引可以分为

  • 聚集索引:基于主键,自动生成的,一般是建表时创建主键.如果没有主键,自动选择唯一键做为聚集索引.
  • 辅助索引:人为创建的(普通,覆盖)
  • 唯一索引:人为创建(普通索引,聚集索引)
# 聚集索引和辅助索引的对比

1.聚集索引:
    叶子结点按照主键列的顺序,存储的整行数据,就是真正的数据页
    
2.辅助索引: 
    叶子节点并不包含行记录的全部数据,而是存储相应行数据的聚集索引键,即主键。当通过辅助索引来查询数据时,InnoDB存储引擎会遍历辅助索引找到主键,然后再通过主键在聚集索引中找到完整的行记录数据,便于回表查询


6.索引管理命令

# 辅助索引(MUL)
根据创建索引时,指定的列的值,进行排序后,存储的叶子节点中
# 好处:
1.优化了查询,减少cpu mem IO消耗
2.减少的文件排序

# 创建普通索引(MUL)
# alter或create
alter table blog_userinfo add key idx_email(email);
create index idx_phone on blog_userinfo(phone);

# 查看索引
# desc或show
desc blog_userinfo;
show index from blog_userinfo;

# 删除索引
# alter或drop
alter table blog_userinfo drop index idx_email;
drop index idx_phone on   blog_userinfo;

# 前缀索引
# 先查看一下多少前缀
select count(*),substring(password,1,20) as sbp  from blog_userinfo group by sbp;
# 在创建前缀索引
alter table blog_userinfo add index idx(password(10));




# 唯一键索引(UNI,如果有重复值是创建不了的)
# 创建
alter table blog_userinfo add unique key uni_email(email);



# 覆盖索引(联合索引)
作用:不需要回表查询,不需要聚集索引,所有查询的数据都从辅助索引中获取
好处:减少回表查询的几率
# 创建 
alter table t1 add index idx_gam(gender,age,money);

# 一旦建立覆盖索引,再次查询时,最好按照创建的顺序查询
# 比如以a,b,c为覆盖索引
# a,b,c效率最高
select *  from  people   where  a,b,c
# a,c,b效率其次
# c或b第一位效率最低


五、执行计划

实际项目开发中,由于我们不知道实际查询的时候数据库里发生了什么事情,数据库软件是怎样扫描表、怎样使用索引的,因此,我们能感知到的就只有

sql语句运行的时间,在数据规模不大时,查询是瞬间的,因此,在写sql语句的时候就很少考虑到性能的问题。但是当数据规模增大,如千万、亿的时候,我们运

行同样的sql语句时却发现迟迟没有结果,这个时候才知道数据规模已经限制了我们查询的速度。所以,查询优化和索引也就显得很重要了。

# 通过查询执行计划,可以知道这个语句到底执行了什么
# explain或desc
explain select * from city where countrycode=\'CHN\'\\G


*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: city
         type: ref
possible_keys: CountryCode,idx_co_po
          key: CountryCode
      key_len: 3
          ref: const
         rows: 1
        Extra: Using index condition
            
            
# 是否是复杂语句      
select_type:SIMPLE
    
# 显示这一行的数据是关于哪张表的
table: city
    
# 显示连接使用了何种类型    
type: ref
    1. 可以判断出,全表扫描还是索引扫描(ALL就是全表扫描,其他的就是索引扫描)
	2. 对于索引扫描来讲,又可以细划分,可以判断是哪一种类的索引扫描
    type的具体类型介绍:
		ALL:全表扫描 
	 		select  *  from  t1;
		Index:全索引扫描
			select countrycode from city ;
		
   		range:索引范围扫描
			where >、<、>=、<=、in、or、between and、like \'CH%\'
		
			# 注:in 或者 or 改写成 union	
            select * from city where countrycode in(\'CHN\',\'USA\')
            
				select * from city where countrycode=\'CHN\'
				union all 
				select * from city where countrycode=\'USA\';
	
		ref:辅助索引的等值查询
			select * from city where countrycode=\'CHN\'
	
		eq_ref:	多表链接查询(join on )
	
		const ,system :主键或唯一键等值查询

# 显示可能应用在这张表中的索引。如果为空,没有可能的索引。
possible_keys: CountryCode,idx_co_po

# 实际使用的索引。
key: CountryCode
    
# 使用的索引的长度。在不损失精确性的情况下,长度越短越好
key_len: 3
    
# 显示索引的哪一列被使用了,如果可能的话,是一个常数
ref: const
    
# MYSQL认为必须检查的用来返回请求数据的行数
rows: 1
    
# 关于MYSQL如何解析查询的额外信息。
Extra: Using index condition
    Distinct :一旦mysql找到了与行相联合匹配的行,就不再搜索了。

	Not exists :mysql优化了LEFT JOIN,一旦它找到了匹配LEFT JOIN标准的行,就不再搜索了。

	Range checked for each Record:没有找到理想的索引,因此对从前面表中来的每一个行组合,mysql检查使用哪个索引,并用它来从表中返回行。这是使用索引的最慢的连接之一。

	Using filesort :看到这个的时候,查询就需要优化了。mysql需要进行额外的步骤来发现如何对返回的行排序。它根据连接类型以及存储排序键值和匹配条件的全部行的行指针来排序全部行。

	Using index :列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候。

	Using temporary :看到这个的时候,查询需要优化了。这里,mysql需要创建一个临时表来存储结果,这通常发生在对不同的列集进行ORDER BY上,而不是GROUP BY上。

	Where used :使用了WHERE从句来限制哪些行将与下一张表匹配或者是返回给用户。如果不想返回表中的全部行,并且连接类型ALL或index,这就会发生,或者是查询有问题。

以上是关于MySQL深入学习--配置索引执行计划的主要内容,如果未能解决你的问题,请参考以下文章

MySQL学习第七篇索引管理及执行计划

性能测试四十一:sql案例之慢sql配置执行计划和索引

深入聊聊MySQL直方图的应用

MYSQL explain执行计划

MYSQL索引的深入学习

(七十四)大白话深入探索多表关联的SQL语句到底是如何执行的?