MySQL到目前为止,算是互联网使用比较广泛的一种持久化数据库,笔者这里对该数据库的使用以及性能优化,做一些总结,方便查漏补缺。
想操作MySQL数据库,那就要先安装数据库环境,不会安装的小伙伴,可以参考我的另外一篇博文:Linux下安装MySQL详细教程
使用MySQL数据库,那么除了使用Hibernate、Mybatis等框架可以操作数据库外,还需要了解下如何使用原生JDBC的实现,因为这些框架底层,还是对JDBC编程进行了封装。
具体的可以参考我的另一篇博文:史上最全的Java JDBC编程详细教程
可参考这里: https://db-engines.com/en/ranking
数据库名称 | 描述 |
---|---|
MySQL | 开源免费的关系型数据库,已经被Oracle收购了,MySQL 5.5版本之后都是由Oracle发布的版本 |
Oracle | 收费的大型数据库,Oracle公司的产品,Oracle收购SUN公司,SUN公司收购了MySQL |
SQL Server | MicroSoft 公司收费的中型的数据库,C#、.net等语言常使用 |
PostgreSQL | 免费开源的功能完善的关系数据库管理系统(ORDBMS),支持多种平台Linux,FreeBSD,OS X,Solaris和Windows等 |
DB2 | IBM公司的数据库产品,收费的,常应用在银行系统中,在国内互联网公司,要求去IOE(IBM小型机、Oracle数据库、EMC存储设备)使用很少 |
SQLite | 嵌入式的小型数据库,应用在手机端、嵌入式设备中 |
SyBase | 已退出历史舞台,提供了一个非常专业的数据建模工具PowerDesigner |
官网: https://www.mysql.com
MySQL常见版本:
版本 | 发布年份 | 备注 |
---|---|---|
5.0.x | 2005 | MyISAM默认存储引擎,存储过程、服务端游标、触发器、查询优化 |
5.1.x | 2008 | 崩溃恢复、Bug修复 |
5.5.x | 2010 | 从该版本开始,是oracle公司发布的; 默认存储引擎更改为InnoDB |
5.6.x | 2013 | 一些优化 |
5.7.x | 2015 | 查询性能提升 |
8.0.x | 2018 | 性能大幅提升 |
所有版本的下载地址:https://downloads.mysql.com/archives/community/
1、MySQL客户端/服务端通信
2、查询缓存
3、查询语句语法解析和预处理
4、查询优化处理
5、执行计划及查询执行引擎
6、返回结果给客户端
MySQL客户端与服务端的通信方式是“半双工
”
全双工: 双向通信,发送同时也可以接收
半双工: 双向通信,同时只能接收或者是发送,无法同时做既发送又接收的操作
单工: 只能单一方向传送
MySQL是半双工通信:
即在任何一个时刻,要么是客户端向服务端发送数据,要么是服务器向客户端发送数据,这两个动作不能同时发生
。
特点和限制:
实际开发中,基本不会用到MySQL的这个查询缓存,因为现在有好多好用的缓存数据库,比如Redis数据库等。
MySQL数据库可以缓存SELECT操作的结果集和SQL语句。当执行新的SELECT语句时,先去查询缓存,判断是否存在可用的结果集,如果存在就使用缓存的结果集。
判断标准:
与缓存的SQL语句是否完全一样,区分大小写,key为查询的sql语句,value为sql语句执行的结果集
eg:
key: select * from xxl_job_log;
value: n条结果集数据
查看是否开启了MySQL缓存,执行以下命令:
show variables like 'query_cache%';
query_cache_type
该配置显示 on 表示缓存打开了,默认mysql是OFF关闭的。
如果需要开启查询缓存,可以修改my.cnf
配置文件:
#config mysql cache
query_cache_type=1
值:0 表示不启用查询缓存,默认值
值:1 表示启用查询缓存,只要符合查询缓存的要求,客户端的查询语句和结果集都可以缓存起来,供其他客户端使用,不过,在查询时可以加上SQL_NO_CACHE
将不使用查询缓存。
eg: select SQL_NO_CACHE * from xxl_job_log;
值:2 表示按需启用查询缓存,只要查询语句中添加了参数:SQL_CACHE,且符合查询缓存的要求,客户端的查询语句和记录集,则可以缓存起来,供其他客户端使用;
select SQL_CACHE * from xxl_job_log;
query_cache_size
允许设置query_cache_size的值最小为40K,默认1M,这是单次查询的结果集大小,超过此值,则不会放入缓存
query_cache_limit
限制查询缓存区最大能缓存的总的查询记录集的大小,默认设置为1M,超过了此大小,则淘汰掉之前的缓存
可查看缓存情况
show status like 'Qcache%';
Qcache_hits:
缓存命中情况;
Qcache_inserts:
缓存插入了多少次
缓存失效:
当该表发生任何一点改变,则与该表的所有缓存都会失效
当查询语句中有一些不确定的数据时,则不会被缓存。如包含函数NOW(),
CURRENT_DATE()等类似的函数,或者用户自定义的函数,存储函数,用户变
量等都不会被缓存
当查询的结果大于query_cache_size设置的值时,结果不会被缓存
对于InnoDB引擎来说,当一个语句在事务中修改了某个表,那么在这个事务提交之前,所有与这个表相关的查询都无法缓存,因此长事务执行会降低缓存命中率
查询的表是系统表
查询语句不涉及到表
如果是一个多写的应用,需要经常让缓存失效,浪费计算资源
查询需要将结果存入查询缓存,也会带来额外的系统消耗
以读为主的业务,数据生成之后就不常改变的业务场景下,比如字典表数据
、报表数据
等。但是,通常情况下,这些数据会丢到Redis缓存中,使用MySQL查询缓存非常少。
MySQL语法解析,将使用MySQL语法规则验证和解析查询,比如是否使用了错误的关键字或者关键字的顺序是否正确等。
通过lex词法分析
,yacc语法分析
将SQL语句解析成解析树
(编译原理),预处理则根据一些MySQL规则进一步检查解析树是否合法,比如检查数据表和数据列是否存在等,验证权限是否合法。
优化器的主要作用就是找到最优的执行计划,那么,查询优化器如何找到最优的执行计划?
使用等价变化规则
1 = 1 and a > 1 ,会被MySQL优化改写成 a > 1
a < b and a = 1 改写成 b > 1 and a = 1
优化count、min、max等函数
EXPLAIN select min(id) from xxl_job_log;
min函数只需找索引最左边(基于索引的B+Tree结构)
EXPLAIN select max(id) from xxl_job_log;
max函数只需找索引最右边(基于索引的B+Tree结构)
myisam引擎count(*)优化,myisam引擎会单独存储数据的总数
子查询优化
EXPLAIN select * from (select * from xxl_job_log where id < 10) t;
提前终止查询
用了limit关键字或者使用不存在的条件
in的优化
先进行排序,再采用二分查找的方式
MySQL查询优化器主要是基于成本计算原则
,它将尝试各种执行计划,采用数据抽样的方式进行试验,找到一个最优的方案去执行,表的数据量不同,可能执行计划也不同。
我们平时在工作中分析SQL语句执行效率,可以根据执行计划进行分析
。
调用插件式存储引擎的API进行执行计划的执行
eg: EXPLAIN select * from xxl_job_log where id = 40;
执行计划ID
ID属性是 mysql 对select查询语句中提供的查询序列号,用于表示本次查询
过程中加载表的顺序或者查询子句执行顺序(标识执行的顺序 )。
执行计划select_type
select_type表示当前查询语句中的查询类型,主要是用于区分普通查询、联合查询、子查询等,主要有以下几种类型:
类型 | 描述 |
---|---|
SIMPLE | 表示当前查询语句是一个简单查询语句,不包含子查询,不包含联合查询,不包含连接查询 |
PRIMARY | 如果执行的是一个包含子查询的查询,或者是一个联合查询,Primary指向外部查询语句或者联合查询中的第一个子查询语句 |
DEPENDENT SUBQUERY | 表示当前查询语句是一个子查询,并且执行条件依赖与外部查询提供的条件 |
SUBQUERY | 表示当前查询是一个子查询,并且这个子查询在执行时不需要得到外部查询的帮助 |
MATERIALIZED | 表示where后面有in条件的子查询 |
UNION | 若第二个select出现在union之后,则被标记为union |
UNION RESULT | 从union表获取结果的select |
示例1:
explain select id from xxl_job_log where id <3 union select id from xxl_job_log where id >= 3;
EXPLAIN
select t2.job_id,t2.trigger_msg,(select max(executor_fail_retry_count) from xxl_job_log t1 where t1.id = t2.id)
from xxl_job_log t2
示例3:
EXPLAIN SELECT * FROM xxl_job_log WHERE id = (SELECT id FROM xxl_job_log WHERE id = 40)
EXPLAIN
select id from xxl_job_log where id >= 100
UNION
select id from xxl_job_log where id < 100;
匹配的分区信息,如果查询基于分区表,将会显示访问的是哪个区。
表分区: 表分区是将一大表,根据条件分割成若干个小表,mysql 5.1开始支持数据表分区,比如:某用户表的记录超过了600万条,那么就可以根据入库日期将表分区,也可以根据所在地将表分区,当然也可根据其他的条件分区。
比如:
partition BY RANGE (uid) (
partition p0 VALUES LESS THAN (10000),
partition p1 VALUES LESS THAN (20000)
);
按照这种分区方案,uid小于10000的所有行被保存在分区P0中,uid在10000到20000的保存在P1中,依次类推。
目前的实际开发中,这个用的不是很多了,有成熟的框架,比如MyCat可以分库分表。
执行计划type
访问类型,sql查询优化中一个很重要的指标,结果值从好到坏依次是:
NULL > system > const > eq_ref > ref > range > index > ALL
类型 | 描述 |
---|---|
NULL | 无需访问表或者索引,比如获取一个索引列的最大值或最小值 |
system | system 是 const 的一种特殊情况,既表本身只有一行数据的情况,基本不会出现,可以忽略不计 |
const | 当查询最多匹配一行时,常出现于 where 条件是=的情况,表示通过索引一次就找到了,const用于比较primary key或者unique索引,可以说是性能最好的 |
eq_ref | 唯一索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见于主键 或 唯一索引扫描,性能次之 |
ref | 非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,性能次之 |
range | 在一个索引上进行范围查找,只检索给定范围的行,使用一个索引来选择行,性能次之,优化时最坏的情况要达到这个级别 |
index | Full Index Scan 索引全表扫描,把索引从头到尾扫描一遍。这种级别进行遍历索引树查询,通常发生在查询结果只包含索引字段时 |
ALL | Full Table Scan,扫描全表以找到匹配的行,没有任何索引可以使用时,这是最差的情况,应该避免 |
表 示 当 前 查 询 语 句 执行时 可 能 用 到 的 索 引 有 哪 些 , 在
possible_keys 中可能出现多个索引,但是这些索引在本次查询
不一定会使用到。
执行计划key
表示当前查询语句真实使用的索引名称,如果这个字段为 null,则有两中可能:一个是当前表中没有索引,二是当前表有索引但是失效了
表示当前索引字段存储内容最大长度。这个长度不是精准值,只是 MySQL 估计的值。这个值越大越精准。在能得到相同结果时,这个值越小那么查询速度越快。
如果本次查询使用了索引,则 key_len 内容不为空。
当使用索引列等值查询时,与索引列进行等值匹配的对象信息
执行计划rows
根据表统计信息或索引选用情况,大致估算出找到所需记录所需要读取的行
数
执行计划filtered
表示返回结果的行数占需读取行数的百分比,filtered的值越大越好,100是最好的情况。
执行计划Extra
Extra记录了十分重要的额外信息,这些额外信息有:
Using filesort:
mysql对数据使用一个外部的文件内容进行了排序,而不是按照表内的索引进行排序读取
Using temporary:
使用临时表保存中间结果,也就是说mysql在对查询结果排序时使用了临时表,比如在order by 或 group by
中间MySql处理过程需要多处理一个临时表,一般这种情况是需要优化处理的。
优化处理一般使用索引优化
Using index:
表示相应的select操作中使用了覆盖索引(Covering Index),避免了访问表的数据行,效率高
Using where:
使用了where过滤条件
select tables optimized away:
基于索引优化MIN/MAX操作或者MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即可完成优化
注:执行计划在MySQL不同版本,所显示的字段可能不一样
服务器硬件
cpu、内存、磁盘io、网卡流量
服务器的操作系统
Linux的配置参数不同性能不同
数据库存储引擎的选择
根据需要选择不同的存储引擎,MyISAM 不支持事务,表级锁,InnoDB 事务级存储引擎,完美支持行级锁和事务ACID特性
数据库自身配置参数
MySQL有上百项的数据库配置参数;(my.cnf)
数据库表结构的设计和SQL语句的执行效率(影响最大的)
慢查询是性能问题的罪魁祸首,不合理的数据库表结构设计和不合理的索引是影响数据库查询性能的重要因素
数据库架构
高并发下读写分离、分库分表、多级缓存、搜索引擎
查看mysql是否开启慢查询日志
show variables like 'slow_query_log';
如果没有开启,需要开启,设置慢查询日志为on(开启):
set global slow_query_log = on
重启后mysql会失效
设置慢查询日志文件的位置:
set global slow_query_log_file = ‘/usr/local/mysql-5.7.24/data/3306/slow-sql.log’
设置查询没有使用索引的情况(开启)
set global log_queries_not_using_indexes = on
设置查询时间大于多少秒的数据记录到慢日志中(默认是10秒),如果设置为0,则会有大量的信息记录到日志中,磁盘容易很快被占满;
set long_query_time=5; (单位秒)
以上条件是或者的关系,只要满足一个条件的都会把日志记录下来
建议将以上配置写在mysql的配置文件中,直接用命令行配置时,在重启mysql后会失效,所以使用配置方式
#config slow query
slow_query_log = on
slow_query_log_file = '/usr/local/mysql-5.7.24/data/3306/slow-sql.log'
log_queries_not_using_indexes = on
long_query_time=5
Time:日志记录的时间
User@Host:执行的用户及主机
Query_time:查询耗费时间,Lock_time 锁表时间,Rows_sent 发送给请求方的记录条数,Rows_examined 语句扫描的记录条数
SET timestamp:语句执行的时间点
select …: 执行的具体语句
最主要看:# Query_time: 0.454651 Lock_time: 0.000095 Rows_sent: 5 Rows_examined: 620016
查看帮助命令: mysqldumpslow --help
示例:
mysqldumpslow -t 10 -s at /usr/local/mysql-5.7.24/data/3306/slow-sql.log
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(30) DEFAULT NULL,
`phone` varchar(25) NOT NULL,
`password` varchar(64) DEFAULT NULL,
`email` varchar(50) DEFAULT NULL,
`account` varchar(15) DEFAULT NULL,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_name_phone` (`name`,`phone`,`create_time`) USING BTREE --联合索引
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
单列索引是特殊的联合索引,是联合索引字段列个数为1的特例。联合索引叶子节点没有存储数据,节省磁盘空间,其数据结构如下:
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(30) DEFAULT NULL,
`phone` varchar(25) NOT NULL,
`password` varchar(64) DEFAULT NULL,
`email` varchar(50) DEFAULT NULL,
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_name_phone` (`name`,`phone`,`create_time`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
联合索引中的每一个索引字段都是where查询的条件;
EXPLAIN select * from users where name ='zhangsan' and phone = '18266668888';
-- 注意:理论上索引对顺序是敏感的,但是由于MySQL的查询优化器会自动调整where子句的条件顺序以使用适合的索引,例如我们将where中的条件顺序颠倒,也用到了索引,效果一样。
EXPLAIN select * from users where phone = '18266668888' and name ='zhangsan';
EXPLAIN select * from users where phone = '18266668888' and name in('zhangsan');
EXPLAIN select * from users where phone in ('18266668888') and name in ('zhangsan');
-- 上面4个都可以命中索引,最后这两个sql,or导致索引失效
EXPLAIN select * from users where name = 'zhangsan' or phone = '18266668888';
EXPLAIN select * from users where phone in ('18266668888') or name in ('zhangsan');
当按照索引中所有列进行精确匹配(这里精确匹配指“=
”或“in
”匹配,每一列都能精确匹配)时,索引可以被用到。
这种情况指的是,只使用联合索引的第一列进行匹配,如果使用索引的第二列或者其他列,而不是用第一列查询,无法命中索引,例如:
EXPLAIN select * from users where name = 'zhangsan';
EXPLAIN select * from users where name = 'zhangsan' and date(create_time) = '2021-06-28';
-- 上面两条sql使用了最左列字段,可以命中索引,而下面这一条,只使用了第二列,无法命中索引
EXPLAIN select * from users where phone = '18266668888';
EXPLAIN select * from users where phone = '18266668888' and date(create_time) = '2021-06-28';
由于不满足最左前缀匹配原则,这样的查询显然不能命中索引。
EXPLAIN select * from users where name = 'zhangsan' and date(create_time) = '2021-06-28';
这种也是可以命中索引,满足最左前缀匹配原则。
这种情况指的是只匹配一个列值的开头部分,比如name=’张三210628’,采用like过滤匹配 zhangsan21%,下面这条sql命中了索引:
EXPLAIN select * from users where name like '张三21%';
注:
1、这里匹配越精确,效率也越高,如果只匹配 zhangsan%,表中有很多这样的数据,那么,mysql查询优化器可能会进行全表扫描。
2、索引一定不是越多越好,一定是创建合适的索引,一张表一般不超过6个。另外,匹配列前缀可用到索引,像like '9999%'可以用到索引,而like ‘%9999%’、like '%9999’用不到索引,比如下面这条sql,就无法命中索引:
EXPLAIN select * from users where name like '%张三';
如果查询中有某个列的范围查询,则右边所有列都无法使用索引查找,比如 where name = 'zhangsan' and phone like '1826666%' and date(create_time) = '2021-06-28',该查询只能使用索引的前两列,优化的话,如果like的结果较少,可以用in。
EXPLAIN select * from users where name = 'zhangsan' and phone like '1826666%' and date(create_time) = '2021-06-28'
假设 phone 只有几个甚至十几个,18266661111、18266662222等等,可优化为下面的写法:(全键值匹配的查询,更精准的定位,读取的数据更少,效率更好)
EXPLAIN select * from users where name = 'zhangsan' and phone in(18266661111,18266662222) and date(create_time) = '2021-06-28'
匹配索引键的第一列范围值(必须是最左前缀),只能命中第一列的范围,范围列后面的列无法用到索引,范围包括 >、<、!=、in、is null、between … and 等。
EXPLAIN select * from users where `name` in ('张三', 'zhangsan21');
EXPLAIN select * from users where phone in ('18266668888', '18266661111');
EXPLAIN select * from users where name = '张三' and phone in ('18266668888', '18266661111');
第一列精确匹配,第二列范围匹配,可以命中索引。
如果查询条件中有函数或表达式,则MySQL不会为该列使用索引。
explain select * from users where id = id - 2;
explain select * from users where left(name, 5) = 'zhangsan';
只查询有索引的字段,这种查询叫“覆盖索引”查询,在索引键中就找到了数据,不需要获取数据行,也就是不需要回表中获取数据,性能得到提高,所以我们对于值需要查询一两个字段的业务,就不要采用select * from,直接使用具体字段查询:
select name, phone, create_time from users where phone = '18266668888';
B+Tree索引是有序的数据结构,所以除了按索引键查询外,还可以按索引键进行order by排序。
如果数据量比较大,可以使用以下sql进行查询优化:
SELECT a.* FROM users where name like 'zhangsan21%' LIMIT 1000000,15;
SELECT a.* FROM users a,(select id from users where name like 'zhangsan21%' LIMIT 3000000,15) b where a.id = b.id
既然索引可以加快查询速度,那么是不是只要是查询语句条件都建上索引?
答案是否定的,索引虽然加快了查询速度,但索引也有代价:
索引文件本身要消耗存储空间,同时索引会加重插入、删除和修改记录时的负担,MySQL在运行时也要消耗资源维护索引,因此索引并不是越多越好。
表记录比较少,例如一两千条甚至只有几百条记录的表,没必要建索引,让查询做全表扫描即可,一般记录在2000条以上可以酌情考虑建立索引。
公式: count(distinct col)/ count(col)
比值越大离散性越好,离散性越好则选择性越好。
索引的离散性较低不建议建索引,离散性较低也就是选择性较低(B+Tree要走很多个分叉去找数据),所谓索引的选择性(Selectivity),是指不重复的索引值(也叫基数,Cardinality)与表记录数(#T)的比值:Index Selectivity = Cardinality / #T
选择性的取值范围为(0, 1],选择性越高的索引价值越大,这是由B+Tree的性质决定的。
eg:
SELECT count(DISTINCT(name))/count(*) AS selectivity FROM users;
如果列的离散性很差,一般创建索引的价值也不大,可以采用联合索引提高列的离散性。
SELECT count(DISTINCT(concat(name, phone)))/count(*) AS selectivity FROM users;
SELECT count(DISTINCT(concat(name, right(phone,4))))/count(*) AS selectivity FROM users;
我们把这个前缀索引建上:
ALTER TABLE `users` ADD INDEX `idx_name_phone` (name, phone(4));
使用前缀索引兼顾了索引大小和查询速度,但其缺点是不能用于ORDER BY和GROUP BY操作,也不能用于覆盖索引Covering index(即当索引本身包含查询所需全部数据时,不再回表访问数据文件本身)。
MySQL有多种存储引擎,存储引擎特征:
使用命令,可以查看存储引擎有哪些:show engines
常见的主要有以下两种。
MyISAM是MySQL 5.5版本之前的默认存储引擎,MySQL中很多系统表也还是使用该存储引擎,系统临时表也会用到MyISAM存储引擎。
特点:
a)select count(*) from table 无需进行数据的扫描
b)数据(MYD)和索引(MYI)分开存储
eg:news.frm-定义表结构文件 news.MYD-数据文件、news.MYT-索引文件
c)表级锁
d)不支持事务
InnoDB是MySQL 5.5及以后版本的默认存储引擎,主要有以下几个特性:
CREATE TABLE `customer` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(30) DEFAULT NULL,
`phone` varchar(25) NOT NULL,
`password` varchar(64) DEFAULT NULL,
`email` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_phone` (`phone`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
CREATE TABLE table1(i INT) ENGINE = CSV;
CREATE TABLE table2(i INT) ENGINE = MEMORY;
https://dev.mysql.com/doc/refman/5.7/en/storage-engines.html
通俗来说,索引就像一本书的目录,根据目录可以快速定位要找的内容所在页码。
使用MySQL索引,可以避免在查询时,对表中所有数据行进行查询,从而提供查询的效率,是提升查询速度的首要选择。
MySQL官方对索引的定义为: 索引(Index)是帮助MySQL高效获取数据的一种数据结构,而且是排好序的数据结构,索引存储在磁盘文件里。
MySQL索引主要有两种结构: B+Tree索引
和Hash索引
我们平常所说的索引,如果没有特别说明,一般都是指B+Tree结构的索引。索引能极大的减少存储引擎需要扫描的数据量,从而提高数据的检索速度。
Normal 普通索引
表示普通索引,大多数情况下都可以使用
Unique 唯一索引
表示唯一的,不允许重复的索引,如果该字段信息不能重复,例如注册手机号用作索引时,可设置为Unique。
Primary Key是拥有自动定义的Unique约束,每个表中可以有多个Unique约束,但是只能有一个Primary Key约束。
Full Text 全文索引
表示全文收索,在检索长文本的时候效果较好,比如搜索一篇文章,在比较短的文本建议使用普通的Index即可
SPATIAL 空间索引
空间索引是对地理空间位置数据类型的字段建立的索引,MYSQL中的地理空间位置数据类型有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON
通过SQL语句给表添加索引
索引命名规范: 非唯一索引名idx_xxx
,唯一索引名uniq_xxx
#使用CREATE 语句创建索引
CREATE INDEX index_name ON table_name(column_name,column_name);
#普通索引
CREATE UNIQUE INDEX index_name ON table_name (column_name);
#非空索引
CREATE PRIMARY KEY INDEX index_name ON table_name (column_name) ;
#使用ALTER TABLE语句创建索引
#添加普通索引
ALTER TABLE`users` ADD INDEX `idx_name`(`idx_name`) USING BTREE;
alter table table_name add index index_columnName (columns) ;
#添加唯一索引
alter table table_name add unique (column_list) ;
alter table table_name add primary key (column_list) ;
Binary Search Trees,算法动画演示:
https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
特点:
左子树的键值小于根的键值,右子树的键值大于根的键值。
对该二叉树的节点进行查找发现深度为1的节点的查找次数为1,深度为2的查找次数为2,深度为3的查找次数为3,深度为n的节点的查找次数为n,因此查找时间复杂度依赖于节点深度,如果节点很深,则查找效率降低。
为了提高二叉查找树的的查找效率,人们引入了一种新的数据结构:平衡二叉树
(也叫AVL树
:1962 年G. M. Adelson-Velsky 和 E. M. Landis两个人提出来的)
Balanced binary search trees(AVL Trees ) 平衡二叉查找树(AVL树)在满足二叉查找树的条件下,还需满足任何节点的两个子树的高度最大差为1,所以它呈现出是一种左右平衡的状态
。
上图中左边的是AVL树,它的任何节点的两个子树的高度差<=1
。而右边的不是AVL树,其根节点的左子树高度为3,而右子树高度为1,两个子树的高度差为2。
当我们向平衡二叉树(AVL Tree)插入新的节点(或者删除新的节点),有可能打破它原有的平衡,那么它会通过旋转使其恢复平衡。当插入新节点,失去平衡的二叉树可以概括为四种状态:LL(左左)
、RR(右右)
、LR(左右)
、RL(右左)
:
LL: LeftLeft,也称“左左”,插入或删除一个节点后,根节点的左孩子(Left Child)的左孩子(Left Child)还有非空节点,导致根节点的左子树高度比右子树高度高2,AVL树失去平衡
RR: RightRight,也称“右右”,插入或删除一个节点后,根节点的右孩子(Right Child)的右孩子(Right Child)还有非空节点,导致根节点的右子树高度比左子树高度高2,AVL树失去平衡
LR: LeftRight,也称“左右”,插入或删除一个节点后,根节点的左孩子(Left Child)的右孩子(Right Child)还有非空节点,导致根节点的左子树高度比右子树高度高2,AVL树失去平衡
RL:RightLeft,也称“右左”,插入或删除一个节点后,根节点的右孩子(Right Child)的左孩子(Left Child)还有非空节点,导致根节点的右子树高度比左子树高度高2,AVL树失去平衡
数据所处的(高)深度决定着他的IO操作次数,数据量越大,树的高度会很高,导致IO操作次数大、耗时大
。当数据量比较大的时候,树的高度依然很高。
注意这里的B-Tree,并不是B减Tree,- 只是一个连接符号,人们经常这么写而已,实际上是B-Tree就是指B Tree(B树)
。
每个节点最大可以存放(路数-1)个关键字信息,每个节点最大可以有(路数)个分支。
以上是3路的B-Tree数据结构在索引中的示意图;
数据查找方式:
X < 17 --> P1
X = 17 命中
17< X < 35 --> P2
X = 35 命中
X > 35 --> P3
模拟查找关键字29的过程:
第一步:根据根节点找到磁盘块1,读入内存,(磁盘I/O操作第1次)(根节点常驻内存)
第二步:比较关键字29所在区间(17,35),找到磁盘块1的指针P2
第三步:根据P2指针找到磁盘块3,读入内存,(磁盘I/O操作第2次)
第四步:比较关键字29所在区间(26,30),找到磁盘块3的指针P2
第五步:根据P2指针找到磁盘块8,读入内存,(磁盘I/O操作第3次)
在磁盘块8中的关键字列表中找到关键字29。
分析上面过程,发现需要3次磁盘I/O操作,和3次内存查找操作。由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率,而3次磁盘I/O操作是影响整个B-Tree查找效率的决定因素,B-Tree相对于AVL Tree缩减了节点个数,使每次磁盘I/O取到内存的数据都发挥了作用,从而提高了查询效率。
扩展: 操作系统会预读数据,需要使用的磁盘片段,周围的数据都会加载进来,即使没有使用,即空间局部性原理。
虽然以上方案已经改善了不少,但是,下面还有更好地方案。
MySQL的InnoDB存储引擎采用的就是B+Tree数据结构实现其索引结构
。
B+Tree是基于B-Tree的,大部分数据结构相同,但也有一些区别:
B+Tree由于非叶子节点不存储数据,那么每个节点就可以存储更多的元素,树的层级更少,所以查询数据更快,所有数据都存在叶子节点,所以每次查找的次数都相同,因而查询速度更稳定
。一种是对于关键字的范围查找和分页查找,另一种是从根节点开始,进行随机查找
举例说明:
相同点
B+树的查找和B树一样,类似于二叉查找树,从根节点出发自顶向下遍历树,在节点内部使用二分查找来确定走哪一边的指针。
不同点
(1)B+树(B+Tree)中间非叶子节点没有数据(索引元素所指向的数据记录),只有索引,而B树(B-Tree)每个结点中的每个关键字都有数据,这样使得同样大小的磁盘页,B+Tree可以容纳更多节点元素,在相同数据量下(1kw),B+树更加“矮胖”,IO操作次数更少,性能也就更好
B树的数据结构:
B+树的数据:
(2)因为数据的不同,导致查询过程也不同,B树的查找只需找到匹配元素即可,最好情况下查找到根节点,最坏情况下查找到叶子结点,所以性能很不稳定,而B+树每次必须查找到叶子结点,性能稳定。
(3)在范围查询方面,B+树的优势更加明显,B树的范围查找需要不断依赖中序遍历。首先二分查找到范围下限,在不断通过中序遍历,直到查找到范围的上限,整个过程比较耗时。而B+树的范围查找则简单许多,首先通过二分查找,找到范围下限,然后同过叶子结点的链表顺序遍历
,直至找到上限即可,效率更高。
小结:
Innodb存储引擎是以页为单位存储数据,默认一个页的大小是16kb(show variables like ‘innodb_page_size’),一般表的主键类型为int(占4个字节)或BigInteger(占8个字节),指针类型也一般为4或8个字节,如果我们都取最大的8个字节,那么一页(B+Tree中的一个节点)中大概存储16kb/(8byte+8byte)=1024个键值,也就是说一个高度为3的B+Tree索引可以存储1025 * 1025 * 1028 ~ 10亿条记录;
实际情况中每个节点可能不能填充满,因此在数据库中,B+Tree的高度一般都在2~4层,它是一个又矮又胖的数据结构
。
所有查询都要查找到叶子节点,查询性能稳定
所有叶子节点形成有序链表,便于范围查询
InnoDB 也是采用 B+Tree数据结构来实现索引,而与MyISAM不同的是InnoDB索引文件和数据文件是存放在一起的,也就是叶节点包含了完整的数据记录,通过索引直接就找到了数据。
eg: user.idb(索引和数据文件)
,users.frm-表的定义文件
InnoDB的辅助索引(Secondary Key)
data域存储相应记录主键的值而不是当前索引键的值,也不是数据地址,换句话说,InnoDB的所有辅助索引都引用主键作为data域,所以对于辅助索引,还需要主键值来回表查询到一条完整记录,因此按辅助索引检索实际上进行了二次查询,效率肯定是没有按照主键检索高的
。
InnoDB索引文件和数据文件是存放在一起的,我们称为聚集索引,但是这里要注意InnoDB存储引擎所谓“聚集”是指主键索引和数据行紧凑地存储在一起,这种情况下才是聚集索引
,如果InnoDB不是主键索引,则索引的叶子节点存储的不是索引的数据,而是该索引行主键的值,那么此时还是非聚集索引。
一般来说,索引文件和数据文件放在一起的是聚集索引,严格来说,聚集指的是主键索引和数据行紧凑地存储在一起。myisam为非聚集索引,InnoDB中主键索引属于聚集索引,二级普通索引(辅助索引)属于非聚集索引
。
InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果InnoDB没有显式指定主键,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形,而且是自动增长。
主键索引,也叫集聚索引,也叫聚簇索引(针对innodb而言)。辅助索引,也叫二级索引,也叫非集聚索引,也叫非聚簇索引
MyISAM索引文件和数据文件是分离的,索引文件仅保存记录所在页的指针(物理位置),通过这些地址来读取页,进而读取被索引的行。即:数据(MYD)和索引(MYI)分开存储 。
eg:news.frm-定义表结构文件 news.MYD-数据文件、news.MYT-索引文件
上图表字段有Col1(ID)、Col2(age)、Col3(name)三个,其中Col1为Primary Key(主键),树中叶子节点保存的是对应行的物理位置,通过该值,存储引擎再回表中进行查询得到一行完整记录,同时每个叶子节点也保存了指向下一个叶子节点的指针,从而方便叶子节点的范围遍历。
如果我们在Col2上建立一个辅助索引,就如上图所示,在MyISAM中,主键索引和辅助索引(Secondary key)在结构上没有任何区别,只是主键索引要求key是唯一的,而辅助索引的key可以重复
。
像上面的 MyISAM的索引方式,不管是主键索引(Primary Key)和辅助索引(Secondary Key)数据与索引是分开的,不在一起,我们称为是“非聚集的”,这种索引我们称为“非聚集索引”。
如果查询列可通过索引节点中的关键字直接返回,则该索引称之为覆盖索引。覆盖索引可减少数据库IO,可提高查询性能。
在使用InnoDB存储引擎时,请永远使用一个与业务无关的自增字段作为主键。
经常也有人采用像手机号、身份证号、uuid这种唯一字段作为主键。从数据库索引优化角度看,使用InnoDB引擎而不使用自增主键绝对不是一个好主意。(顺序的结构,自增顺序,方便查找数据)
InnoDB使用聚集索引,数据记录本身被存于主键索引(一颗B+Tree)的叶子节点上,这就要求同一个叶子节点内的各条数据记录按主键顺序存放,当有一条新的记录插入时,MySQL会根据其主键将其插入适当的节点位置,如果使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页。
这样就会形成一个紧凑的索引结构,近似顺序填满,每次插入也不需要移动已有数据,效率较高,索引维护开销小。如果使用非自增主键(如手机号、身份证号等),每次插入主键的值近似于随机,因此每次新纪录都要被插到现有索引页得中间某个位置:
此时MySQL不得不为了将新记录插到合适位置而移动数据,增加了开销,同时频繁的移动、分页操作造成了大量的碎片,得到了不紧凑的索引结构。所以尽量在InnoDB上采用自增字段做主键;
哈希索引是采用哈希算法,把键值换算成hash值
,比如做如下的查询:
select * from table where Col2 = 89;
优点:
首先将89进行hash运算得到文件指针index,然后根据文件指针index映射磁盘存储引用,这样查找数据直接找到磁盘数据位置(即一次hash运算就找到数据所在位置),查找数据非常快,比B+Tree效率高。
但是hash 非常大的不足: 即不能排序、不能范围查找
,比如我们查找Col2>34的数据,它需要给每个范围值进行hash运算然后找到数据所在磁盘位置,效率不如B+Tree,B+Tree索引是排好序的,在实际开发中经常有范围查询,所以大部分情况下都是采用B+Tree索引
。
Hash这种索引结构,只能针对 字段名=目标值 的场景使用,不适合模糊查询(like)、范围查询 等场景。
select DATE_FORMAT(NOW(),'%Y-%m-%d %H:%i:%s') from dual;#2021-06-28 22:59:19
#date_add() 增加 date_sub()减少 month 月份 minute 分钟 second 秒
#eg: update user set leverstart=now(),leverover=date_add(NOW(), interval 1 MONTH) where id=1;
select DATE_SUB(NOW(),interval 1 month) lastMonthTime;#2019-06-29 10:17:18
select DATE_FORMAT(DATE_SUB(NOW(),interval 1 month),'%Y-%m-%d') lastMonthTime;#2019-06-29
select curdate();# 获取当前日期 2019-07-29
select day(curdate());# 获取当前日 29
select last_day(curdate()) lastDayMonth;# 获取当月最后一天。
select DATE_SUB(LAST_DAY(CURDATE()),INTERVAL 1 month);# 获取上个月最后一天 2019-06-30
select DATE_ADD(curdate(),interval -day(curdate())+1 day) firstDayMonth;# 获取本月第一天 2019-07-01
select DATE_ADD(DATE_SUB(CURDATE(),INTERVAL 1 month),interval -day(curdate())+1 day) firstDayLastMonth;# 获取上个月第一天 2019-06-01
select DATE_SUB(CURDATE()-day(CURDATE())+1, interval 1 month);# 获取上个月第一天 2019-06-01
select date_add(curdate()-day(curdate())+1,interval 1 month);# 获取下个月的第一天
#DATEDIFF函数 DATEDIFF函数计算两个DATE,DATETIME或TIMESTAMP值之间的天数
select date_add(curdate()-day(curdate())+1,interval 1 month);# 2019-08-01
select DATE_ADD(curdate(),interval -day(curdate())+1 day);# 2019-07-01
select DATEDIFF(date_add(curdate()-day(curdate())+1,interval 1 month ),DATE_ADD(curdate(),interval -day(curdate())+1 day)) from dual; #获取当前月的天数
-- ---------------------------------------------------------
#获取当前时间是星期几
select date_format(curdate(),'%w') from dual;
select DATE_SUB(curdate(),INTERVAL date_format(curdate(),'%w')-7 day) as 'date';
SELECT t1.* FROM users t1
WHERE t1.name= 'zhangsan' AND t1.is_delete = 0
AND t1.id = (SELECT t2.id FROM users t2 WHERE t2.is_delete = 0 AND t2.name= 'zhangsan' AND t2.real_name = t1.real_name ORDER BY t2.create_time DESC LIMIT 1);
写博客是为了记住自己容易忘记的东西,另外也是对自己工作的总结,希望尽自己的努力,做到更好,大家一起努力进步!
如果有什么问题,欢迎大家一起探讨,代码如有问题,欢迎各位大神指正!
给自己的梦想添加一双翅膀,让它可以在天空中自由自在的飞翔!