关于mysql优化:06期使用-OPTIMIZERTRACE-窥探-MySQL-索引选择的秘密

优化查问语句的性能是 MySQL 数据库治理中的一个重要方面。在优化查问性能时,抉择正确的索引对于缩小查问的响应工夫和进步零碎性能至关重要。然而,如何确定 MySQL 的索引抉择策略?MySQL 的优化器是如何抉择索引的? 在这篇《索引生效了?看看这几个常见的状况!》文章中,咱们介绍了索引区分度不高可能会导致索引生效,而这里的“不高”并没有具体量化,实际上 MySQL 会对执行打算进行老本估算,抉择老本最低的计划来执行。具体咱们还是通过一个案例来阐明。 案例还是以人物表为例,咱们来看一下优化器是怎么抉择索引的。 建表语句如下: CREATE TABLE `person` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(64) NOT NULL, `score` int(11) NOT NULL, `age` int(11) NOT NULL, `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_name_score` (`name`,`score`) USING BTREE, KEY `idx_age` (`age`) USING BTREE, KEY `idx_create_time` (`create_time`) USING BTREE) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;而后插入 10 万条数据: create PROCEDURE `insert_person`()begin declare c_id integer default 3; while c_id <= 100000 do insert into person values(c_id, concat('name',c_id), c_id + 100, c_id + 10, date_sub(NOW(), interval c_id second)); -- 须要留神,因为应用的是now(),所以对于后续的例子,应用文中的SQL你须要本人调整条件,否则可能看不到文中的成果 set c_id = c_id + 1; end while;end;CALL insert_person();能够看到,最早的 create_time 是 2023-04-14 13:03:44。 ...

April 21, 2023 · 3 min · jiezi

关于mysql优化:一张图看懂-SQL-的各种-join-用法

下图展现了 LEFT JOIN、RIGHT JOIN、INNER JOIN、OUTER JOIN 相干的 7 种用法。 具体合成如下: 1.INNER JOIN(内连贯) SELECT <select_list> FROM Table_A AINNER JOIN Table_B BON A.Key = B.Key2.LEFT JOIN(左连贯) SELECT <select_list>FROM Table_A ALEFT JOIN Table_B BON A.Key = B.Key3.RIGHT JOIN(右连贯) SELECT <select_list>FROM Table_A ARIGHT JOIN Table_B BON A.Key = B.Key4.OUTER JOIN(外连贯) SELECT <select_list>FROM Table_A AFULL OUTER JOIN Table_B BON A.Key = B.Key5.LEFT JOIN EXCLUDING INNER JOIN(左连贯-内连贯) SELECT <select_list> FROM Table_A ALEFT JOIN Table_B BON A.Key = B.KeyWHERE B.Key IS NULL6.RIGHT JOIN EXCLUDING INNER JOIN(右连贯-内连贯) ...

August 5, 2022 · 1 min · jiezi

关于mysql优化:你真的对MySQL数据类型了解吗

MySQL反对的数据类型十分多,抉择正确的数据类型对于取得高性能至关重要。本文将介绍MySQL的数据类型,以及通过数据类型简略介绍对应的开发标准。 注:在本章节中所提到的严格模式,指的是STRICT_TRANS_TABLES和STRICT_ALL_TABLES两个中的一个启用或者都启用。 1 . 抉择优化的数据类型MySQL反对的数据类型有很多,抉择正确的数据类型对于取得高性能至关重要。咱们在抉择数据类型上,有几个简略的准则。 更小的通常更好个别状况下,应该尽量应用能够正确存储数据的最小数据类型。例如,只须要存100以内的整数,TINYINT更好。更小的数据类型通常更快,因为它们占用更少的磁盘、内存和CPU缓存,解决时须要的CPU周期也更少。 然而要确保没有低估须要存储的值的范畴。因为在schema中的多个中央减少数据类型的范畴是一个十分耗时和苦楚的操作。如果无奈确定哪个数据类型是最好的,就抉择你认为不会超过范畴的最小类型(如果零碎不是很忙或者存储的数据量不多,或者是在能够轻易批改设计的晚期阶段,这时候批改数据类型比拟容易)。 简略就好简略数据类型的操作通常须要更少的CPU周期。例如,整型比字符操作代价更低,因为字符集和校对规定(排序规定)使字符比拟比整型比拟更简单。比方:应该应用MySQL内建的类型(DATE,TIME,DATETIME)而不是字符串来存储日期和工夫;以及应该用整型存储IP地址。 尽量避免NULL很多表都蕴含可为NULL(空值)的列,即便应用程序并不需要保留NULL也是如此。这是因为可为NULL是列的默认属性(如果定义表构造时没有指定列为NOT BULL,默认都是容许为NULL的)。通常状况下,最好指定列为NOT NULL,除非真的须要存储NULL值。 如果查问中蕴含可为NULL的列,对MySQL更难优化。因为可为NULL的列使得索引、索引统计和值比拟都更简单。可为NULL的列会应用更多的存储空间,在MySQL里也须要非凡解决。当可为NULL的列被索引时,每个索引记录须要一个额定的字节,在MyISAM里甚至还可能导致固定大小的索引(例如只有一个整数列的索引)变成可变大小的索引。 通常把可为NULL的列改为NOT NULL带来的性能晋升比拟小,所以(调优时)没有必要首先在现有schema中查找并批改掉这种状况,除非确定这会导致问题。然而,如果打算在列上建索引,就应该尽量避免设计成可为NULL的列。 当然也有例外,例如,InnoDB应用独自的位(bit)存储NULL值,所以对于稠密数据(很多值为NULL,只有少数行的列有非NULL值)有很好的空间效率。但这一点不适用于MyISAM。 下一步是抉择具体类型。很多MySQL的数据类型能够存储雷同类型的数据,只是存储的长度和范畴不一样、容许的精度不同,或者须要的物理空间(磁盘和内存空间)不同。雷同大类型的不同子类型数据有时也有一些非凡的行为和属性。 例如,DATETIME和TIMESTAMP列都能够存储雷同类型的数据:工夫和日期,准确到秒。然而TIMESTAMP只应用DATETIME一半的存储空间,并且会依据时区变动,具备非凡的自动更新能力。另一方面,TIMESTAMP容许的工夫范畴要小得多,有时候它的非凡能力会成为阻碍。 2 . 整数类型整数类型是数据库中最根本的数据类型,包含整数和实数。如果存储整数,能够应用:TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT。别离应用8、16、24、32、64位存储空间。它们能够存储的值的范畴从-2(N-1)到2(N-1)-1,其中N是存储空间的位数。 MySQL能够为整数类型指定宽度。例如INT(11),对大多数利用这是没有意义的:它不会限度值的非法范畴,只是规定了用来显示字符的宽度。对于存储和计算来说,INT(1)和INT(20)是雷同的。 整数类型有两个属性:UNSIGNED和ZEROFILL。 整数类型有可选的UNSIGNED属性,示意不容许负值,这大抵能够使负数的下限进步一倍。例如TINYINT UNSIGNED能够存储的范畴是0 ~ 255,而TINYINT的存储范畴是-128 ~ 127。 ZEROFILL是数字前须要填充一些0值的时候应用的。例如‘0001’,‘0002’,比方咱们常常看到的股票代码。在应用ZEROFILL参数时,MySQL会主动为该列增加UNSIGNED属性。ZEROFILL也只是一个显示属性,底层存储仍是一般整数。如果存储的数据长度超过ZEROFILL定义的宽度时,此数据会被残缺的显示进去而不进行0的填充;如果存储的数据长度小于设定的宽度,则主动填充0。 3 . 实数类型实数是带有小数局部的数字。然而,它们不只是为了存储小数局部;也能够应用DECIMAL存储比BIGINT还大的整数。MySQL既反对准确类型,也反对不准确类型。 FLOAT和DOUBLE类型是用来示意近似数值的数据类型,应用规范的浮点运算进行近似计算。单精度浮点数(FLOAT)应用4个字节存储,双精度浮点数(DOUBLE)应用8个字节存储。 MySQL容许非标准语法:FLOAT(M,D),DOUBLE(M,D)。M和D别离示意精度和标度。M是数据的总长度,D是小数点后的保留长度。比方,定义为FLOAT(7,4)的一个列能够显示为-999.9999。MySQL在保留值时会进行四舍五入。因而在FLOAT(7,4)列内插入999.00009的近似后果是999.0001。 如果插入值的精度高于理论定义的精度,零碎会主动进行四舍五入解决,使插入的值合乎咱们的定义。所以在一些须要准确小数的状况下,比方:财务工资类型这种场景,请不要应用FLOAT和DOUBLE。 从MySQL 8.0.17开始,不倡议应用非标准语法,并且在未来的MySQL版本中将删除对FLOAT(M,D)和DOUBLE(M,D)的反对。 SQL规范容许在关键字FLOAT前面的括号内用来指定精度(但不能为指数范畴),就是FLOAT(p)。FLOAT(p)中的p也是示意精度(以位数示意),但MySQL只应用该值来确定列的数据类型为FLOAT或DOUBLE。当 0≤p≤24 时,MySQL 把它当成 FLOAT 类型,当 25≤p≤53 时,MySQL 把它当成 DOUBLE 型。 咱们发现:此种类型的FLOAT只能保障前6位整数不四舍五入,而FLOAT(M,D)则不会这样。 DECIMAL和NUMERIC类型存储准确的数值类型,比方财务数据、足球比赛中的赔率等等。在MySQL中,NUMERIC是以DECIMAL来实现的。因而无关DECIMAL的阐明同样实用于NUMERIC。 MySQL中DECIMAL以二进制格局存储值。每4个字节存9个数字。这种存储形式对于整数与小数局部是离开存储的。每9个数字须要4个字节,剩下的数字所需的存储空间如下所示: 图4-1 举例来说,DECIMAL(18,9)小数点两边各有9个数字,因而整数和小数局部别离各须要4个字节,小数点自身占1个字节。DECIMAL(20,6)有14个整数和6个小数,整数局部中的9个数字须要4个字节,剩下的5个数字须要3个字节;小数局部6个数字须要3个字节。 在DECIMAL列申明中,能够(通常是)指定精度和小数位数。例如:salary DECIMAL(5,2)。其中,5是精度,2是小数位数。精度示意值存储的有效位数,小数位数示意小数点后能够存储的位数。 规范语法要求DECIMAL(5,2)可能存储具备五位数字和两位小数的任何值。因而能够存储在salary列中的值的范畴是从-999.99到999.99。 在规范语法中,语法DECIMAL(M)等价于DECIMAL(M,0)。MySQL也反对这种变体。默认的M是10。 如果小数位数是0,表明DECIMAL不含小数局部。 小数点和正数的‘-’符号不包含在M中。DECIMAL反对的M为65,D是30。如果调配给此类型的值小数点后位数超过指定的标度D容许的范畴,值将按标度D进行转换(准确的行为是特定于操作系统的,然而通常是将其截断为容许的位数)。 DECIMAL列不存储结尾的+、-和0数字。如果你向DECIMAL(5,1)的列中插入+0003.1,MySQL会存储为3.1。对于正数,‘-’字符不会被存储。 NUMERIC和FIXED都是DECIMAL的同义词。 咱们能够通过试验来看下DECIMAI数据类型。 官网介绍的DECIMAL是高精度类型,但当产生截断时,也会呈现四舍五入的景象。所以在设置精度和标度的时候要足够长,不让它产生截断数据的操作。 因为CPU不反对对DECIMAL的间接计算,所以在MySQL5.0及更高版本中,MySQL服务器本身实现了DECIMAL的高精度计算。相对而言,CPU间接反对原生浮点计算,所以浮点运算显著更快。 因为须要额定的空间和计算开销,所以应该尽量只在对小数进行准确计算时才应用DECIMAL,例如存储财务数据。但在数据量比拟大的时候,能够思考应用BIGINT代替DECIMAL,将须要存储的货币单位依据小数的位数乘以相应的倍数即可。假如要存储财务数据准确到万分之一分,则能够把所有金额乘以一百万,而后将后果存储在BIGINT中,这样能够同时防止浮点存储计算不准确和DECIMAL准确计算代价高的问题。 ...

June 23, 2022 · 1 min · jiezi

关于mysql优化:MySQL优化学习手札四-单表访问方法

本篇是介绍MySQL执行打算的铺垫,明天终于想好了该如何组织这部分内容,先是大抵介绍查问的实现,再由此引出执行打算。概述咱们日常的查问,根本能够分为三类: 单表查问子查问连贯查问这三种能够组合,也能够离开,下面的程序也是咱们学习SQL的程序,咱们上面介绍其实现,也是依照下面这种程序。看本篇之前倡议先看这个本篇的前几篇: MySQL优化学习手札(一)MySQL优化学习手札(二)MySQL优化学习手札(三)当然如果你对MySQL的B+树索引比拟相熟也能够不看。 单表拜访办法咱们写了一个单表查问的语句,MySQL是如何获取咱们查问语句所对应的记录的呢: SELECT * FROM Student WHERE ID = 1;咱们疏忽语法解析、连贯建设这些步骤,这些都搞定了,那么MySQL该如何定位记录呢?MySQL中执行查问的形式一共有以下两种: 全表扫描(一条记录一条记录的去比拟)应用索引进行查问,索引也有不同的类型,所以就算是应用索引进行查问,也分为几种不同的状况: 针对主键或惟一二级索引的等值查问针对一般二级索引的等值查问针对索引列的范畴查问间接扫描整个索引在MySQL中执行查问语句的形式称之为拜访办法或者拜访类型。 通过主键列等值匹配来定位记录-constSELECT * FROM Student WHERE ID = 1; ID是主键Id是Student这张表的主键,这里让咱们在回顾一下MySQL存储数据的根本构造: InnoDB将数据划分为若干页,以页作为磁盘和内存之间交互的根本单位,InnoDB中页的大小个别为16KBInnoDB存储引擎会主动为主键(如果没有它会主动帮咱们增加)建设聚簇索引,聚簇索引的叶子结点蕴含残缺的用户记录。每个索引都对应一颗B+树,B+树分为好多层,最下边一层是叶子结点,其余的是内结点。所有的用户记录都存储在B+树的叶子结点。所有的目录项记录都存储在内结点咱们也能够为本人感兴趣的列建设二级索引,二级索引的叶子结点蕴含的用户记录由索引列+主键组成,所以如果想通过二级索引来查找残缺的用户记录的话,须要通过回表操作,也就是在通过二级索引找到主键值之后再到聚簇索引中查找残缺的用户记录。B+树的每层结点都是依照索引列值的从小达到的程序排序而组成了双向链表,而每个页内的记录(不论是用户记录还是目录项记录)都是依照索引列的值从小达到的程序而造成了一个单链表。如果是联结索引的话,则页面和记录先依照联结索引前边的列排序,如果该列的值雷同,再依照联结索引后边的列进行排序。 也就是ID这一列是聚簇索引,同时按ID进行排序,所以这个相当快,能够先用定位到目录项地行为这一列位于哪个数据页,定位到之后,再用二分查找定位这条记录在哪个地位。当初让咱们为Student再加上一列name,并为该列建设惟一索引,咱们去执行如下查问: select * from student where name ='aa'也同聚簇索引相似,然而name列因为是非聚簇索引列,叶子结点没有残缺的记录,定位到name= ‘aa’这条记录后,还用用这条记录对应的主键去主键索引列去查残缺的记录。即使有回表的代价,MySQL的开发人员依然认为这种查问形式是十分快的,将这种拜访办法定义为: const,也就是常数级别。但如果查问NULL值,状况就又有所不同: select * from student where name is null因为惟一索引并不限度NULL值的数量,所以下面的查问语句可能会拜访到多条记录。 ref用惟一索引列去查找NULL值,会查问到多行,这种状况和用非惟一索引列去匹配记录相似,查问步骤和用惟一索引列去查问是一样的,定位这条记录在哪个数据页,而后到具体的页外面去匹配记录,因为咱们是select * , 所以还要回表查问。 如果匹配的记录比拟少,回表的代价还是比拟低的,MySQL就更偏向于应用索引+回表的形式来查找,采纳二级索引进行等值查问记录的形式,MySQL将其定义为ref。 但对于某个蕴含多个索引列的二级索引列来说,只有最右边的间断索引列是与常数的等值比拟就可能采纳ref的拜访办法。至于为什么是可能起因在于还是老本的掂量,如果你是select * , 这就要回表。如果查问的记录比拟多,让MySQL感觉与其索引列+回表还不如间接扫描全表的话。 当初让咱们再为Student再加: age, sex,同时为age、sex建一个一般的索引。如果咱们查问的语句写成了上面这样: select * from student where age = '18' and sex > '女'这种拜访办法在MySQL中的查问级别就不是ref,起因在于对sex这一列应用的是范畴查问。 ref_or_null依据一般索引进行匹配,但同时查找该索引列为null的值,像上面这样: select * from student where name = 'aa' and name is null这种查问级别在MySQL中咱们称之为ref_or_null. ...

April 16, 2022 · 2 min · jiezi

关于mysql优化:三高Mysql-Mysql索引和查询优化偏理论部分

引言 内容为慕课网的"高并发 高性能 高可用 MySQL 实战"视频的学习笔记内容和集体整顿扩大之后的笔记,本节内容讲述的索引优化的内容,另外本局部内容波及很多优化的内容,所以学习的时候倡议打开《高性能Mysql》第六章进行回顾和理解,对于Mysql数据的开发同学来说大抵理解外部工作机制是有必要的。 因为文章内容过长,所以这里拆分为两局部,高低局部的内容均应用sakila-db,也就是mysql的官网案例。第一局部讲述优化的实践和Mysql过来的优化器设计的缺点,同时会介绍更高的版本中如何修复欠缺这些问题的(然而从集体看来新版本那些优化基本算不上优化,甚至有的优化还是照抄的Mysql原作者的实现的,倒退了这么多年才这么一点问题还是要归功于Oracle这种极致商业化公司的功绩)。 如果内容比拟难,能够追随《Mysql是怎么样运行》集体读书笔记专栏补补课,集体也在学习和同步更新中。 地址如下:https://juejin.cn/column/7024...。 【知识点】Mysql索引内容的介绍索引的应用策略和应用规定查问优化排查,简略理解Mysql各个组件的职责前置筹备sakila-db sakila-db是什么?国外很火的一个概念,指的是国外的电影租赁市场应用租赁的形式进行电影的观看非常受外国的喜爱。这里介绍是因为后续的内容都用到了这个案例。所以咱们须要提前把相干的环境筹备好,从如下地址进行下载: 下载地址:https://dev.mysql.com/doc/ind... 《高性能Mysql》的SQL 案例也是应用官网的example work-bench work-bench是官网开发的数据库关系图的可视化工具,应用官网案例的具体关系图展现成果如下,通过这些图能够看到Sakila-db之间的大抵关系: work-bench也是开源免费软件,下载地址如下: https://dev.mysql.com/downloa... 装置workbench和下载sakila-db的形式这里不做记录,在运行的时候须要留神先建设一个数据库运行sheme文件,而后执行data的sql文件,最终在navicat中查看数据: 注释局部索引类型 首先是索引的特点以及作用: 索引的目标是为了晋升数据的效率。对于ORM框架来说索引的应用至关重要,然而ORM的优化往往难以顾及所有业务状况,后续被逐步废除。不同的索引类型实用于不同的场景。索引关键在于缩小数据须要扫描的量,同时防止服务器外部对内容排序和长期表(因为长期表会索引生效),随机IO转程序IO等特点 上面介绍Mysql相干的索引类型: 哈希索引:哈希索引适宜全值匹配和准确查找,查问的速度十分快 在MySQL中只有memory存储引擎显式反对此索引,memory还反对非惟一哈希索引的,是哈希索引设计外面比拟非凡的。空间索引:空间索引是myisam表反对,次要用作天文数据存储,这里蕴含一个叫做GIS的玩意,然而GIS在Postgre中应用比MySQL要杰出很多,所以mysql中空间索引是无关紧要的货色。全文索引:全文索引也是myisam独有反对的一种索引类型。适宜应用的场景为全值匹配的场景和关键字查问,对于大文本的关键字匹配能够无效解决。聚簇索引:聚簇索引是innodb存储引擎的默认存储引擎。前缀压缩索引:留神这个索引针对的是myisam存储引擎,目标是为了让索引放入内存中排序,,前缀压缩的办法是首先保留索引块的第一个值,而后在保留第二个值,存储第二个值相似(长度,索引值)的模式寄存前缀索引。其余索引类型注意事项: Archive 在5.1之后才反对单列自增索引。 MyISAM 反对压缩之后的前缀索引,使得数据结构占用更小。 哈希索引 在Mysql中惟一显式实现哈希索引的存储引擎为Memory,Memory是存在非惟一哈希索引,同时BTree也反对“自适应哈希索引的形式“兼容哈希索引。 上面是哈希索引特点: 键存储的是索引哈希值,留神不是索引值自身,而值存储的是指向行的指针留神此哈希索引无奈防止行扫描,然而在内存中指针十分快通常能够忽略不计留神只有哈希值依照程序排序,然而行指针不是依照程序排序哈希不反对:局部索引笼罩,只反对全索引笼罩,因为应用全副的索引列计算哈希值哈希索引反对等值匹配操作不反对范畴查问,比方等于,in子查问,不全等。如果呈现哈希抵触,哈希索引将进化为链表程序查问,同时保护索引的开销也会变大聚簇索引 聚簇示意数据行的值紧凑存储在一起。而innodb聚簇的值就是主键的值,所以通常应用都是主键上的索引,针对主键索引的抉择非常重要。因为本局部着重索引优化,聚簇索引这里就不再讲述了。 MyISam和Innodb的主键索引区别是MyISam的索引很简略,因为数据行只蕴含行号,所以索引间接存储列值和行号,数据独自寄存另一处,相似于一个惟一非空索引,索引和数据不在一处,MyISam的索引设计比InnoDB简略很多,这和MyIsam不须要反对事务也有间接关系,而innodb将索引和行数据放入一个数据结构,将列进行紧凑的存储。 聚簇索引有上面长处 紧凑存储数据行,所以能够只扫描大量磁盘就能够获取到数据数据拜访的速度十分快,索引和数据放在同一颗BTree中,比非聚簇索引查问快很多笼罩索引能够间接缩小回表当然索引也有上面的毛病: 对于非IO密集型利用,聚簇索引的优化无意义。插入速度依赖于插入程序,然而如果不是自增插入则须要optimize table从新组织表。更新代价十分高,因为BTree要保障程序排序须要移动数据页地位和指针。主键数据插入过满数据页存在页决裂问题,行溢出会导致存储压力加大。聚簇索引导致全表扫描变慢,页决裂导致数据问题等。二级索引须要回表查问聚簇索引能力查问数据。二级索引因为须要存储主键开销会更大,至多在InnoDb中保护一个二级索引的开销是挺大的。压缩索引 压缩索引的特点是应用更少的空间寄存尽可能多的内容,然而这样的解决形式仅仅实用于IO密集型的零碎,压缩前缀存储模式最大的缺点是无奈应用二分法进行查找,同时如果应用的倒序索引的形式比方order by desc 的形式可能会因为压缩索引的问题存在卡顿的状况。 Bree索引的特点 叶子结点存在逻辑页和索引页两种,通常非最底层叶子结点都是索引页,最底层索引页由链表串联。Btree索引会依据建表程序对于索引值进行排序,索引建表时候倡议将常常查问的字段往前挪。Btree索引适宜的查问类型:前缀查问,范畴查问,键值查问(哈希索引)。自适应哈希索引 当innodb发现某些索引列和值应用频繁的时候,BTree会在此基础上主动创立哈希索引辅助优化,然而这个行为是不受内部管制的,齐全是外部的优化行为,如果不须要能够思考敞开。 Btree查问类型 针对Innodb的Btree索引,有上面几种常见的查问形式: 全值匹配:等值匹配的形式,全值匹配适宜哈希索引进行查问最左匹配准则:二级索引的查问条件放在where最右边前缀匹配:只应用索引的第一列,并且like ‘xxx%’范畴匹配:范畴匹配索引列到另一列之间的值范畴查问和准确匹配联合,一个全值匹配,一个范畴匹配笼罩索引查问:笼罩索引也是一种查问形式,索引策略 上面是对于建设索引的一些常见策略: 第一件事件须要思考的是预测那些数据为热点数据或者热点列,依照《高性能Mysql》介绍,对于热点列来说有时候要违反最大选择性的准则,通过建设时常搜寻的索引作为最左前缀的默认的设置。同时优化查问须要思考所有的列,如果一个查问的优化会毁坏另一个查问,那么就须要优化索引的构造。第二件事件是思考where的条件组合,通过组合多种where条件,须要思考的是尽可能让查问重用索引而不是大规模的建设新索引。防止多个范畴进行扫描,一方面是范畴查问会导致,然而对于多个等值的条件查问,最好的方法是尽量管制搜寻范畴。 对于索引的策略咱们还须要理解上面的细节 ...

April 5, 2022 · 3 min · jiezi

关于mysql优化:深入理解Mysql索引优化查询

1.多张表查问的索引剖析2.在什么状况下适宜/不适宜建设索引3.如何防止索引生效4.查问优化 1.多张表查问的索引剖析 假如咱们当初有三张表: CREATE TABLE `t_student` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(255) COLLATE utf8_bin DEFAULT NULL COMMENT '学生名字', `create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='学生表';CREATE TABLE `t_class` ( `id` int NOT NULL AUTO_INCREMENT, `class_name` varchar(255) COLLATE utf8_bin DEFAULT NULL, `create_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='课程表';CREATE TABLE `t_score` ( `id` int NOT NULL AUTO_INCREMENT, `student_id` int DEFAULT NULL, `class_id` int DEFAULT NULL, `score` int DEFAULT NULL COMMENT '得分', `create_time` datetime DEFAULT NULL, `update_time` datetime DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='分数表';假如咱们要对这三张表进行联查,如何能力建设优良的索引? ...

April 2, 2022 · 2 min · jiezi

关于mysql优化:霜皮剥落紫龙鳞下里巴人再谈数据库SQL优化索引一级二级聚簇非聚簇原理

原文转载自「刘悦的技术博客」https://v3u.cn/a_id_206 举凡后端面试,面试官不言数据库则已,言则必称SQL优化,说起SQL优化,网络上各种“指南”和“圣经”难以枚举,不一而足,好像SQL优化未然是妇孺皆知的实践常识,而后依据少数无知(Pluralistic ignorance)实践,人们印象里感觉少数人会怎么想怎么做,但这种印象往往是不精确的。那SQL优化到底应该怎么做?本次让咱们褪去SQL富丽的躯壳,以最通俗,最粗鄙,最下里巴人的形式解说一下SQL优化的前因后果,前世今生。 SQL优化背景首先要明确一点,SQL优化不是为了优化而优化,就像冬天要穿羽绒服,不是因为有羽绒服或者羽绒服自身而穿,是因为天儿太冷了!那SQL优化的起因是什么?是因为SQL语句太慢了!从狭义上讲,SQL语句蕴含增删改查,但个别的业务场景下,SQL的读写比例应该是一比十左右,而且写操作很少呈现性能问题,即便呈现,大多数也是慢查问阻塞导致。生产环境中遇到最多的,也是最容易出问题的,还是一些简单的查问操作,所以查问语句的优化显然是第一要务。 那咱们怎么晓得那条SQL慢?开启慢查问日志(slow\_query\_log) 将 slow\_query\_log 全局变量设置为“ON”状态 mysql> set global slow_query_log='ON';设置慢查问日志寄存的地位 mysql> set global slow_query_log_file='c:/log/slow.log';查问速度大于1秒就写日志: mysql> set global long_query_time=1;当然了,这并不是标准化流程,如果是实时业务,500ms的查问兴许也算慢查问,所以个别须要依据业务来设置慢查问工夫的阈值。 当然了,本着“防微杜渐”的准则,在慢查问呈现之前,咱们齐全就能够将其扼杀在摇篮中,那就是写出一条sql之后,应用查问打算(explain),来理论检查一下查问性能,对于explain命令,在返回的表格中真正有决定意义的是rows字段,大部分rows值小的语句执行并不需要优化,所以基本上,优化sql,实际上是在优化rows,值得注意的是,在测试sql语句的效率时候,最好不要开启查问缓存,否则会影响你对这条sql查问工夫的正确判断: SELECT SQL_NO_CACHESQL优化伎俩(索引)除了防止诸如select *、like、order by rand()这种陈词滥调的低效sql写法,更多的,咱们依附索引来优化SQL,在应用索引之前,须要弄清楚到底索引为什么能帮咱们进步查问效率,也就是索引的原理,这个时候你的脑子里必定浮现了图书的目录、火车站的车次表,是的,网上都是这么说的,事实上是,如果没坐过火车,没有应用过目录,那这样的生存索引样例就并不直观,作为下里巴人,咱们肯定吃过包子: 毫无疑问,当咱们在吃包子的时候,其实是在吃馅儿,如果没有馅儿,包子就不是包子,而是馒头。那么问题来了,我怎么保障一口就能吃到馅儿呢?这里的馅儿,能够了解为数据,海量数据的包子,可能直径几公里,那么我怎么能疾速失去我想要的数据(馅儿)?有生存教训的吃货肯定会通知你,找油皮儿,因为馅儿外面有油脂,更贴近包子皮儿的中央,或者包子皮儿簙的中央,都会被油脂渗透,也就造成了油皮儿,所以如果照着油皮儿下嘴,至多要比咬其余中央更容易吃到馅儿,那么,索引就是油皮儿,有索引的数据就是有油皮儿的大包子,没有索引的数据就是没有油皮儿的大包子,如此一来,索引的原理不言而喻,通过放大数据范畴(油皮儿)来筛选出最终想要的后果(馅儿),同时把随机的查问(轻易咬)变成程序的查问(先找油皮儿),也就是咱们总是通过同一种查问形式来锁定数据。 SQL索引的数据结构B+tree晓得了背景,理解了原理,当初咱们须要某种容器(数据结构)来帮咱们实现包子的油皮儿,这种容器能够帮助咱们每次查找数据时把咬包子次数管制在一个很小的数量级,最好是常数数量级。于是B+tree闪亮退场。 那么,假如数据库中有1-7条数据,一次查问,B+tree到底怎么帮咱们疾速检索到数据呢? SELECT SQL_NO_CACHE id from article where id = 4 如图所示,如果要查找数据4,那么首先会把B+tree的根节点加载到内存,此时产生一次咬包子(IO读操作),在内存中用二分查找确定4在3和5之间,通过根节点所存储的指针加载叶子节点(3,4)到内存中,产生第二次咬包子,完结查问,总计两次。如果不应用索引,咱们须要咬四口包子能力把4咬进去。而在生产环境中,2阶的B+树能够示意上百万的数据,如果上百万的数据查找只须要两次IO读操作,性能进步将是微小的,如果没有索引,每个数据项都要产生一次IO读取,那么总共须要百万次的IO,显然老本是微小的。 同时,咱们晓得IO次数读写取决于B+树的层级,也就是高度h,假如以后数据表的数据为N,每个存储容器的数据项的数量是m,则有h=㏒(m+1)N,当数据量N肯定的状况下,m越大,h越小;而m = 存储容器的大小 / 数据项的大小,存储容器的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量的小,比方int占4字节,要比bigint8字节少一半。这也是为什么B+树要求把实在的数据放到叶子节点而不是非叶子节点,一旦放到非叶子节点,存储容器的数据项会大幅度降落,导致树的层数增高。当数据项等于1时将会进化成线性表,又变成了程序查找,所以这也是为啥索引用B+tree,而不必B-tree,根本原因就是叶子节点存储数据高度就会减小,而高度减小能力帮咱们更快的吃到馅儿。 说白了就是B-tree也能实现索引,也能让咱们更快的拜访数据,然而B-tree每个节点上都带着一点儿馅儿,而这个馅儿占据了原本油皮的空间,所以为了扩容,只能减少B-tree的高度进行扩容,随着馅儿越来越多,导致B-tree的高度也越来越高,高度越高,咱们咬包子的次数也越来越频繁,读写效率则越来越慢。 当B+树的数据项是复合的数据结构,即所谓的联结索引,比方(name,age,sex)的时候,B+树是依照从左到右的程序来建设搜寻树的,比方当(小明,20,男)这样的数据来检索的时候,B+树会优先比拟name来确定下一步的所搜方向,如果name雷同再顺次比拟age和sex,最初失去检索的数据;但当(20,男)这样的没有name的数据来的时候,B+树就不晓得下一步该查哪个节点,因为建设搜寻树的时候name就是第一个比拟因子,必须要先依据name来搜寻能力晓得下一步去哪里查问。比方当(小明,F)这样的数据来检索时,B+树能够用name来指定搜寻方向,但下一个字段age的缺失,所以只能把名字等于小明的数据都找到,而后再匹配性别是男的数据了, 这个是十分重要的性质,即索引的最左匹配个性,对于最左准则能够参照这篇文章:mysql联结索引的最左前缀准则以及b+tree。 最根本的索引建设准则无外乎以下几点: 1.最左前缀匹配准则,十分重要的准则,mysql会始终向右匹配直到遇到范畴查问(>、<、between、like)就进行匹配,比方a = 1 and b = 2 and c > 3 and d = 4 如果建设(a,b,c,d)程序的索引,d是用不到索引的,如果建设(a,b,d,c)的索引则都能够用到,a,b,d的程序能够任意调整。 2.=和in能够乱序,比方a = 1 and b = 2 and c = 3 建设(a,b,c)索引能够任意程序,mysql的查问优化器会帮你优化成索引能够辨认的模式。 ...

February 14, 2022 · 2 min · jiezi

关于mysql优化:索引下推yyds

索引的问题,曾经跟大家聊了两篇文章了~明天再聊一个索引下推问题,也是十分有意思! 索引下推是从 MySQL5.6 开始引入一个个性,英文是 index condition pushdown,个别简称为 ICP,索引下推通过缩小回表的次数,来进步数据库的查问效率。 有的小伙伴可能也看过一些对于 ICP 的概念,然而我感觉,概念比较简单,说一下很容易懂,然而在理论利用中,各种各样的状况十分多。所以接下来的内容我想通过几个具体的查问剖析来和大家分享 ICP 到底是怎么一回事。 1. 索引下推为了给大家演示索引下推,我用 docker 装置了两个 MySQL,一个是 MySQL5.5.62,另一个是 5.7.26,因为索引下推是 MySQL5.6 中开始引入的新个性,所以这两个版本就能够给大家演示出索引下推的特点(不懂 docker 的小伙伴能够在公众号后盾回复 docker,有松哥写的入门教程)。 1.1 筹备工作首先我有如下一张表: CREATE TABLE `user2` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `age` int(11) DEFAULT NULL, `address` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL, PRIMARY KEY (`id`), KEY `username` (`username`(191),`age`)) ENGINE=InnoDB AUTO_INCREMENT=100001 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;我在 MySQL5.5 和 MySQL5.7 中别离执行如上 SQL,确保两个 MySQL 中都有这样一张表。这张表中有一个由 username 和 age 组成的复合索引,索引名字就叫 username,在本文接下来的内容中,我说 username 索引就是指该复合索引。 ...

January 24, 2022 · 2 min · jiezi

关于mysql优化:阿里云分布式数据库-PolarDBX-手册学习

官网文档:《云原生分布式数据库 PolarDB-X》 -- 物理执行打算 EXPLAIN EXECUTE select * from order_goods limit 100 -- 表构造 show create table lp_order_goods -- 全局二级索引CREATE GLOBAL INDEX `g_i_order_id` ON order_goods (`order_id`) dbpartition by hash(`order_id`);

December 10, 2021 · 1 min · jiezi

关于mysql优化:如何使用WGCLOUD监测MySQL运行中慢SQL

WGCLOUD是一款轻量实用的运维监控工具,以笨重、性能优良、易扩大、指标全、集成度高、部署简略、易上手等特点 下载:www.wgstart.com 装置步骤网站有比拟具体的阐明,此处不在形容,咱们间接进入正题,如何应用wgcloud监测MySQL运行时候的慢sql数量 **1.装置好后,咱们点击左侧菜单数据监控->数据源治理,咱们先增加数据源,点击增加** ** 2.数据源增加胜利后,点击左侧菜单【数据表治理】,增加数据表,点击增加按钮** show global status like 'slow_queries' 在数据库客户端执行后果 这是查问慢sql的语句,将返回的列名Value输出到【sql执行后果的取值列名】即可 数据表监控,只能监测返回后果为一行,获取一列为数字的列值,如果返回多行零碎默认取第一行 **3.增加实现后,零碎默认每1小时扫描一次数据表,会逐步造成趋势图,如下,提醒:能够在server/config/application.yml批改扫描时间**

September 28, 2021 · 1 min · jiezi

关于mysql优化:Mysql设计与开发规范

设计规范以下所有标准会依照【高危】、【强制】、【倡议】三个级别进行标注,恪守优先级从高到低 对于不满足【高危】和【强制】两个级别的设计,DBA有权力强制打回要求批改 库名1.【强制】库的名称必须管制在32个字符以内,相干模块的表名与表名之间尽量体现join的关系,如user表和user_login表 2.【强制】库的名称格局:业务零碎名称_子系统名,同一模块应用的库名尽量应用对立前缀 3.【强制】个别分库名称命名格局是库通配名_编号,编号从0开始递增,比方wenda_001以工夫进行分库的名称格局是“库通配名_工夫” 4.【强制】创立数据库时必须显式指定字符集,并且字符集只能是utf8或者utf8mb4。创立数据库SQL举例:create database db1 default character set utf8; 表构造1.【强制】表必须有主键,且设置id为自增主键 2.【强制】表禁止应用外键,如果要保障残缺下,应由程序端实现,外键使表之间互相耦合,影响update、delete等性能,有可能造成死锁,高并发环境下容易导致数据库性能瓶颈 3.【强制】表和列的名称必须管制在32个字符以内,表名只能应用字母、数字和下划线,一律小写。如表名过长能够采纳缩写等形式 4.【强制】创立表时必须显式指定字符集为utf8或utf8mb4 5.【强制】创立表时必须显式指定表存储引擎类型,如无非凡需要,一律为InnoDB。当须要应用除InnoDB/MyISAM/Memory以外的存储引擎时,必须通过DBA审核能力在生产环境中应用。因为Innodb表反对事务、行锁、宕机复原、MVCC等关系型数据库重要个性,为业界应用最多的MySQL存储引擎。而这是其余大多数存储引擎不具备的,因而首推InnoDB 6.【强制】建表必须有comment,表级别和字段级别都要有comment 7.【倡议】建表时对于主键:(1)强制要求主键为id,类型为int或bigint(为了当前延展性,这里要求新建表对立为bigint),且为auto_increment(2)标识表里每一行主体的字段不要设为主键,倡议设为其余字段如user_id,order_id等,并建设unique key索引。因为如果设为主键且主键值为随机插入,则会导致innodb外部page决裂和大量随机I/O,性能降落 8.【倡议】外围表(如用户表,金钱相干的表)必须有行数据的创立工夫字段create_time和最初更新工夫字段update_time,便于查问题 9.【倡议】表中所有字段必须都是NOT NULL default 默认值 属性,业务能够依据须要定义DEFAULT值。因为应用NULL值会存在每一行都会占用额定存储空间、数据迁徙容易出错、聚合函数计算结果偏差以及索引生效等问题 10.【倡议】倡议对表里的blob、text等大字段,垂直拆分到其余表里,仅在须要读这些对象的时候才去select 11.【倡议】反范式设计:把常常须要join查问的字段,在其余表里冗余一份。如user_name属性在user_account,user_login_log等表里冗余一份,缩小join查问 12.【强制】两头表用于保留两头后果集,名称必须以tmp_结尾。备份表用于备份或抓取源表快照,名称必须以bak_结尾。两头表和备份表定期清理 13.【强制】对于线上执行DDL变更,必须通过DBA审核,并由DBA在业务低峰期执行 列数据类型优化1.【倡议】表中的自增列(auto_increment属性),举荐应用bigint类型。因为无符号int存储范畴为-2147483648~2147483647(大概21亿左右),溢出后会导致报错 2.【倡议】业务中选择性很少的状态status、类型type等字段举荐应用tinytint或者smallint类型节俭存储空 3.【倡议】业务中IP地址字段举荐应用int类型,不举荐用char(15)。因为int只占4字节,能够用如下函数互相转换,而char(15)占用至多15字节。一旦表数据行数到了1亿,那么要多用1.1G存储空间。 SQL:select inet_aton('192.168.2.12'); select inet_ntoa(3232236044); PHP: ip2long(‘192.168.2.12’); long2ip(3530427185); 4.【倡议】不举荐应用enum,set。 因为它们节约空间,且枚举值写死了,变更不不便。举荐应用tinyint或smallint 5.【倡议】不举荐应用blob,text等类型。它们都比拟节约硬盘和内存空间。在加载表数据时,会读取大字段到内存里从而节约内存空间,影响零碎性能。倡议和PM、RD沟通,是否真的须要这么大字段 6.【倡议】存储金钱的字段,倡议用int,程序端乘以100和除以100进行存取。或者用decimal类型,而不要用double 7.【倡议】文本数据尽量用varchar存储。因为varchar是变长存储,比char更省空间。MySQL server层规定一行所有文本最多存65535字节 8.【倡议】工夫类型尽量选取datetime。而timestamp尽管占用空间少,然而有工夫范畴为1970-01-01 00:00:01到2038-01-01 00:00:00的问题 索引设计1.【强制】InnoDB表必须主键为id int/bigint auto_increment,且主键值禁止被更新 2.【倡议】惟一键以“uk_”或“uq_”结尾,一般索引以“idx_”结尾,一律应用小写格局,以字段的名称或缩写作为后缀 3.【强制】InnoDB和MyISAM存储引擎表,索引类型必须为BTREE;MEMORY表能够依据须要抉择HASH或者BTREE类型索引 4.【强制】单个索引中每个索引记录的长度不能超过64KB 5.【倡议】单个表上的索引个数不能超过5个 6.【倡议】在建设索引时,多思考建设联结索引,并把区分度最高的字段放在最后面。如列userid的区分度可由select count(distinct userid)计算出来 7.【倡议】在多表join的SQL里,保障被驱动表的连贯列上有索引,这样join执行效率最高 8.【倡议】建表或加索引时,保障表里相互不存在冗余索引。对于MySQL来说,如果表里曾经存在key(a,b),则key(a)为冗余索引,须要删除 分库分表、分区表1.【强制】分区表的分区字段(partition-key)必须有索引,或者是组合索引的首列 2.【强制】单个分区表中的分区(包含子分区)个数不能超过1024 3.【强制】上线前RD或者DBA必须指定分区表的创立、清理策略 4.【强制】拜访分区表的SQL必须蕴含分区键 5.【倡议】单个分区文件不超过2G,总大小不超过50G。倡议总分区数不超过20个 6.【强制】对于分区表执行alter table操作,必须在业务低峰期执行 7.【强制】采纳分库策略的,库的数量不能超过1024 ...

August 19, 2021 · 1 min · jiezi

关于mysql优化:MySQL优化之分析SQL执行过程explainshow-profiletrace

转自MySQL优化之剖析SQL执行过程(explain、show profile、trace)

May 8, 2021 · 1 min · jiezi

关于mysql优化:MySQL-binlog-远程备份方法详解

以前备份binlog时,都是先在本地进行备份压缩,而后发送到近程服务器中。然而这其中还是有肯定危险的,因为日志的备份都是周期性的,如果在某个周期中,服务器宕机了,硬盘损坏了,就可能导致这段时间的binlog就失落了。 而且,以前用脚本对近程服务器进行备份的形式,有个毛病:无奈对MySQL服务器以后正在写的二进制日志文件进行备份。所以,只能等到MySQL服务器全副写完能力进行备份。而写完一个binlog的工夫并不固定,这就导致备份周期的不确定。 从MySQL5.6开始,mysqlbinlog反对将近程服务器上的binlog实时复制到本地服务器上。 mysqlbinlog的实时二进制复制性能并非简略的将近程服务器的日志复制过去,它是通过MySQL 5.6颁布的Replication API实时获取二进制事件。站长博客实质上,就相当于MySQL的从服务器。与一般服务器相似,主服务器产生事件后,个别都会在0.5~1秒内进行备份。 备份命令 复制代码 代码如下:mysqlbinlog --read-from-remote-server --raw --host=192.168.244.145 --port=3306 --user=repl --password=repl --stop-never mysql-bin.000001 解释如下: --read-from-remote-server:用于备份近程服务器的binlog。如果不指定该选项,则会查找本地的binlog。 --raw:binlog日志会以二进制格局存储在磁盘中,如果不指定该选项,则会以文本模式保留。 --user:复制的MySQL用户,只须要授予REPLICATION SLAVE权限。 --stop-never:mysqlbinlog能够只从近程服务器获取指定的几个binlog,也可将一直生成的binlog保留到本地。指定此选项,代表只有近程服务器不敞开或者连贯未断开,mysqlbinlog就会一直的复制近程服务器上的binlog。 mysql-bin.000001:代表从哪个binlog开始复制。 除了以上选项外,还有以下几个选项须要留神: --stop-never-slave-server-id:在备份近程服务器的binlog时,mysqlbinlog实质上就相当于一个从服务器,该选项就是用来指定从服务器的server-id的。默认为-1。 --to-last-log:代表mysqlbinlog不仅可能获取指定的binlog,还能获取其后生成的binlog,获取完了,才终止。如果指定了--stop-never选项则会隐式关上--to-last-log选项。 --result-file:用于设置近程服务器的binlog,保留到本地的前缀。譬如对于mysql-bin.000001,如果指定--result-file=/test/backup-,则保留到本地后的文件名为/test/backup-mysql-bin.000001。留神:如果将--result-file设置为目录,则肯定要带上目录分隔符“/”。譬如--result-file=/test/,而不是--result-file=/test,不然保留到本地的文件名为/testmysql-bin.000001。 有余: 这个形式有个问题,对于惯例的主从复制来说,如果主从间接的连贯断开了,则从会主动再次连贯,而对于mysqlbinlog,如果断开了,并不会主动连贯。 解决方案: 可通过脚本来补救上述有余。 !/bin/shBACKUP_BIN=/usr/bin/mysqlbinlog LOCAL_BACKUP_DIR=/backup/binlog/ BACKUP_LOG=/backup/binlog/backuplog REMOTE_HOST=192.168.244.145 REMOTE_PORT=3306 REMOTE_USER=repl REMOTE_PASS=repl FIRST_BINLOG=mysql-bin.000001 time to wait before reconnecting after failureSLEEP_SECONDS=10 create local_backup_dir if necessarymkdir -p ${LOCAL_BACKUP_DIR} cd ${LOCAL_BACKUP_DIR} 运行while循环,连贯断开后期待指定工夫,从新连贯while : do if [ ls -A "${LOCAL_BACKUP_DIR}" |wc -l -eq 0 ];then LAST_FILE=${FIRST_BINLOG} else ...

January 20, 2021 · 1 min · jiezi

关于mysql优化:mysql的这些坑你踩过吗快来看看怎么优化mysql

什么是mysql?如果你的答复是关系型数据库,那就会显得有些肤浅。咱们平时工作中必定会用到mysql,然而谈到mysql,就不能只说增删改查。接下来咱们从另一个角度认识一下mysql(其实不仅仅是mysql,对于任何一个产品、服务,咱们都应该有一个抽象化的架构,而不能局限于这个产品的某一个区域)mysql的逻辑分层 连贯层:提供客户端的连接功能和权限认证,服务层: 提供用户应用的接口(curd,主从配置,数据备份等)sql优化器(mysql query optimizer)# 联结索引 a b c select * from table1 where a=xxx and c=xxx and b=xxx;#通过优化器优化后能够应用索引,引擎层 :提供存储数据的形式(innodb myisam archive ,memory,csv,federated ),Mysql在V5.1之前默认存储引擎是MyISAM;在此之后默认存储引擎是InnoDB,myisam 和innodb的区别:https://segmentfault.com/a/11... mysql> show engines -> ;+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+| Engine | Support | Comment | Transactions | XA | Savepoints |+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+| InnoDB | DEFAULT | Supports transactions, row-level locking, and foreign keys | YES | YES | YES || MRG_MYISAM | YES | Collection of identical MyISAM tables | NO | NO | NO || MEMORY | YES | Hash based, stored in memory, useful for temporary tables | NO | NO | NO || BLACKHOLE | YES | /dev/null storage engine (anything you write to it disappears) | NO | NO | NO || MyISAM | YES | MyISAM storage engine | NO | NO | NO || CSV | YES | CSV storage engine | NO | NO | NO || ARCHIVE | YES | Archive storage engine | NO | NO | NO || PERFORMANCE_SCHEMA | YES | Performance Schema | NO | NO | NO || FEDERATED | NO | Federated MySQL storage engine | NULL | NULL | NULL |+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+mysql> show variables like '%storage_engine%';+----------------------------------+--------+| Variable_name | Value |+----------------------------------+--------+| default_storage_engine | InnoDB || default_tmp_storage_engine | InnoDB || disabled_storage_engines | || internal_tmp_disk_storage_engine | InnoDB |+----------------------------------+--------+TODO 具体存储引擎和相干应用场景待补充 ...

December 4, 2020 · 16 min · jiezi

关于mysql优化:掰扯下Innodb内存架构之BufferPool

咱们都晓得mysql中的数据最终都会存到磁盘上,而咱们又晓得磁盘的读写速度和cpu不是在一个数量级上,所以咱们猜想mysql存储引擎里必然有缓冲这一概念,本节咱们就好好掰扯掰扯myslq中的缓存.先到官网看看,存储innodb存储引擎的构造从上图咱们能够看到,INNODB架构,大抵分为内存局部构造和磁盘局部构造, 而内存局部又有BufferPool,ChangeBuffer,Adaptive Hash Index,LogBuffer等.咱们将依照本图内存局部构造一点一点掰扯每一个缓存局部.3:首先来看BufferPool说白了就是内存里一个最次要的缓存数据,并且在专用服务器上(这里能够了解mysql的生产环境) 80%的物理内存都调配给了buffer pool。 可见buffer的重要性。那数据在buffer里是怎么存储的呢? 在看官网给的架构图 先不细剖析这个构造,大抵看了下就是应用了LRU(least recently used 这个大家都很相熟吧.不相熟自行补充)算法. 并且把整个列表分了new和old两局部. 还有一点就是列表里每一项到底缓存数据的维度是什么?所以上面将分3个局部介绍 1:buffer pool 缓存的数据维度是什么? 2:为什么LRU算法还有new和old局部,为什么会这样设计? 3:实战看下buffer pool相干的配置 1:buffer pool 缓存的数据维度是什么? When room is needed to add a new page to the buffer pool, the least recently used page is evicted and a new page is added to the middle of the list. 从官网这句话,咱们能够得悉每一项都是一页数据 . 这个前期写mysql索引文章的时候,会详细分析为什么会是页数据为单位. 2:为什么LRU算法还有new和old局部,为什么会这样设计? 其实大家想想只有LRU算法的毛病就晓得了. 看下图 就是随着新的数据页进来,列表中的数据会被大量置换进来. 没有达到缓存的目标. 其实官网也介绍了哪些状况下会呈现这种状况[见下图]. 说白了,全表扫描,什么mysqldump操作,select语句没有where条件,都会把大量数据放进buffer bool,即时这些数据并不是热点数据, 但同时还会把热点数据给排斥进来. 那减少了new和old局部,当呈现下面操作,又是怎么爱护热点数据呢? 可从上图看出,新读入的数据都放在old列表,并且不会替换掉new列表中的热点数据. 那问题来了,什么时候old列表中数据会进入new列表呢? 其实是进入buffer pool的数据,能被真正读取到, 并停留肯定工夫窗口,才会进入new列表 [有点像jvm内存分代,多大年龄能够进入老年代]. ...

November 29, 2020 · 1 min · jiezi

关于mysql优化:MySQL优化3explain分析执行计划字段说明

应用explain的12个字段阐明 0. 前情提要: 用到的几个表阐明:0.1. goods表和goods2两个表构造和数据雷同(复制的表)-test库mysql> show create table goods;CREATE TABLE `goods` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(10) DEFAULT NULL, `price` double DEFAULT NULL, UNIQUE KEY `id` (`id`)) ENGINE=InnoDB AUTO_INCREMENT=150001 DEFAULT CHARSET=utf8mysql> show create table goods2;CREATE TABLE `goods2` ( `id` bigint(20) unsigned NOT NULL DEFAULT '0', `name` varchar(10) CHARACTER SET utf8 DEFAULT NULL, `price` double DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4mysql> select count(*) from goods;+----------+| count(*) |+----------+| 150000 |+----------+1 row in set (0.02 sec)mysql> select count(*) from goods2;+----------+| count(*) |+----------+| 150000 |+----------+1 row in set (0.05 sec)0.2. t2表是goods表中的前10条-test库mysql> select * from t2;+----+---------+--------+| id | name | price |+----+---------+--------+| 1 | 商品1 | 200.17 || 2 | 商品2 | 200.87 || 3 | 商品3 | 200.81 || 4 | 商品4 | 200.43 || 5 | 商品5 | 200.73 || 6 | 商品6 | 200.36 || 7 | 商品7 | 200.61 || 8 | 商品8 | 200.98 || 9 | 商品9 | 200.06 || 10 | 商品0 | 200.38 |+----+---------+--------+10 rows in set (0.00 sec)mysql> show create table t2;CREATE TABLE `t2` ( `id` bigint(20) unsigned NOT NULL DEFAULT '0', `name` varchar(10) CHARACTER SET utf8 DEFAULT NULL, `price` double DEFAULT NULL, UNIQUE KEY `id` (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb40.3. sakila库的film表:mysql> show create table film;CREATE TABLE `film` ( `film_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT, `title` varchar(128) NOT NULL, `description` text, `release_year` year(4) DEFAULT NULL, `language_id` tinyint(3) unsigned NOT NULL, `original_language_id` tinyint(3) unsigned DEFAULT NULL, `rental_duration` tinyint(3) unsigned NOT NULL DEFAULT '3', `rental_rate` decimal(4,2) NOT NULL DEFAULT '4.99', `length` smallint(5) unsigned DEFAULT NULL, `replacement_cost` decimal(5,2) NOT NULL DEFAULT '19.99', `rating` enum('G','PG','PG-13','R','NC-17') DEFAULT 'G', `special_features` set('Trailers','Commentaries','Deleted Scenes','Behind the Scenes') DEFAULT NULL, `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`film_id`), KEY `idx_title` (`title`), KEY `idx_fk_language_id` (`language_id`), KEY `idx_fk_original_language_id` (`original_language_id`), CONSTRAINT `fk_film_language` FOREIGN KEY (`language_id`) REFERENCES `language` (`language_id`) ON UPDATE CASCADE, CONSTRAINT `fk_film_language_original` FOREIGN KEY (`original_language_id`) REFERENCES `language` (`language_id`) ON UPDATE CASCADE) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8mb4;1 row in set (0.00 sec)mysql> select count(*) from film;+----------+| count(*) |+----------+| 1000 |+----------+1 row in set (0.00 sec)0.4. sakila库的film_category表mysql> show create table film_actor;CREATE TABLE `film_actor` ( `actor_id` smallint(5) unsigned NOT NULL, `film_id` smallint(5) unsigned NOT NULL, `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`actor_id`,`film_id`), KEY `idx_fk_film_id` (`film_id`), CONSTRAINT `fk_film_actor_actor` FOREIGN KEY (`actor_id`) REFERENCES `actor` (`actor_id`) ON UPDATE CASCADE, CONSTRAINT `fk_film_actor_film` FOREIGN KEY (`film_id`) REFERENCES `film` (`film_id`) ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;mysql> select count(*)from film_actor;+----------+| count(*) |+----------+| 5462 |+----------+1 row in set (0.00 sec)0.5. sakila库的film_category表mysql> show create table film_category;CREATE TABLE `film_category` ( `film_id` smallint(5) unsigned NOT NULL, `category_id` tinyint(3) unsigned NOT NULL, `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`film_id`,`category_id`), KEY `fk_film_category_category` (`category_id`), CONSTRAINT `fk_film_category_category` FOREIGN KEY (`category_id`) REFERENCES `category` (`category_id`) ON UPDATE CASCADE, CONSTRAINT `fk_film_category_film` FOREIGN KEY (`film_id`) REFERENCES `film` (`film_id`) ON UPDATE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 mysql> select count(*) from film_category;+----------+| count(*) |+----------+| 1000 |+----------+1 row in set (0.00 sec)0.6. sakila库的actor表mysql> show create table actor;CREATE TABLE `actor` ( `actor_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT, `first_name` varchar(45) NOT NULL, `last_name` varchar(45) NOT NULL, `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`actor_id`), KEY `idx_actor_last_name` (`last_name`)) ENGINE=InnoDB AUTO_INCREMENT=201 DEFAULT CHARSET=utf8mb4 mysql> select count(*) from actor;+----------+| count(*) |+----------+| 200 |+----------+1 row in set (0.00 sec)explain一共有12个字段, 上面具体介绍: ...

September 12, 2020 · 13 min · jiezi

关于mysql优化:mysql优化a技巧篇not-in等3种类型的sql优化

1. not in 和 <> 的优化-->left joinSELECT customer_id, first_name, last_name, email FROM customerWHERE customer_id NOT IN (SELECT customer_id FROM payment);--优化后SELECT a.customer_id, a.first_name, a.last_name, a.emailFROM customer aLEFT JOIN payment b ON a.customer_id=b.customer_idWHERE b.customer_id IS NULL;2. 大表的数据批改要分批解决比方1000万行记录在表中要删除, 或者更新100万行记录; 优化计划: 一次只删除/删除5000条, 而后sleep几秒(暂停几秒)-->给主从同步缓冲工夫; 3. 汇总查问的优化 select count(*)-> 减少汇总表减少一个汇总表, 每天更新一次汇总值; 而后每次查问时, 汇总表+实时表(time>date(now()))即可; SELECT COUNT(*) FROM product_comment WHERE product_id=999;--减少汇总表 product_comment_cntCREATE TABLE product_comment_cnt(product_id INT, cnt INT);--查问时:SELECT SUM(cnt) FROM( SELECT cnt FROM product_comment_cnt WHERE product_id = 999 UNION ALL SELECT count(*) FROM product_comment WHERE product_id = 999 AND timestr>DATE(now()));--mysql> select DATE(now());+-------------+| DATE(now()) |+-------------+| 2020-09-12 |+-------------+1 row in set (0.01 sec)

September 12, 2020 · 1 min · jiezi

关于mysql优化:MySQL优化2慢查询日志工具ptquerydigest

1. pt-query-digest简介第三方工具. perl脚本; 能够剖析 binlog, general log, slowlog; 也能够通过 show processlist 或者 通过 tcpdump 抓取MySQL协定数据来进行剖析. 能够把剖析后果输入到文件中. 剖析过程是 先对查问语句的条件 参数化, 而后对参数化的查问进行分组统计, 统计出各个查问的执行工夫, 次数, 占比等, 能够借助剖析后果, 找出问题, 进而进行优化. 2. pt-query-digest装置2.1 装置perl模块须要装置perl模块 yum install -y perl-CPAN per-Time_HiRes2.2 percona上下载 percona-toolkit工具https://www.percona.com/downl... wget https://www.percona.com/downl... 下载rpm包, 下载实现后: yum localinstall -y percona-toolkit-3.2.1-1.el7.x86_64.rpm装置即可 2.3 装置遇到的问题: libmysqlclient.so我装置中遇到: 谬误:软件包:perl-DBD-MySQL-4.023-6.el7.x86_64 (base) 须要:libmysqlclient.so.18(libmysqlclient_18)(64bit)谬误:软件包:perl-DBD-MySQL-4.023-6.el7.x86_64 (base) 须要:libmysqlclient.so.18()(64bit)您能够尝试增加 --skip-broken 选项来解决该问题您能够尝试执行:rpm -Va --nofiles --nodigest 解决形式: 找到mysql装置的tar包, 装置这两个: [root@niewj download]# rpm -ivh mysql-community-libs-5.7.31-1.el7.x86_64.rpm [root@niewj download]# rpm -ivh mysql-community-libs-compat-5.7.31-1.el7.x86_64.rpm [root@niewj download]#yum localinstall -y --skip-broken percona-toolkit-3.2.1-1.el7.x86_64.rpm最初装置 percona-toolkit就能够了! ...

September 11, 2020 · 9 min · jiezi

关于mysql优化:mysql优化1慢查询日志工具mysqldumpslow

1. mysqldumpslow简介mysql装置好后自带的, perl工具. 2. 查看命令用法:mysqldumpslow --help[root@niewj download]# mysqldumpslow --helpUsage: mysqldumpslow [ OPTS... ] [ LOGS... ]Parse and summarize the MySQL slow query log. Options are --verbose verbose --debug debug --help write this text to standard output -v verbose -d debug -s ORDER what to sort by (al, at, ar, c, l, r, t), 'at' is default al: average lock time ar: average rows sent at: average query time c: count l: lock time r: rows sent t: query time -r reverse the sort order (largest last instead of first) -t NUM just show the top n queries -a don't abstract all numbers to N and strings to 'S' -n NUM abstract numbers with at least n digits within names -g PATTERN grep: only consider stmts that include this string -h HOSTNAME hostname of db server for *-slow.log filename (can be wildcard), default is '*', i.e. match all -i NAME name of server instance (if using mysql.server startup script) -l don't subtract lock time from total time[root@niewj download]# 3. mysqldumpslow参数之-(1): -v或--verbose打印明细信息 ...

September 10, 2020 · 5 min · jiezi

关于mysql优化:mysql优化预备篇开启慢查询日志

1. 查看慢查问是否开启-- 查看慢查问日志开启否 show variables like 'slow_query_log';slow_query_log OFF2. 开启慢查问记录-- 开启慢查问日志记录 set global slow_query_log=on;1 queries executed, 1 success, 0 errors, 0 warnings查问:set global slow_query_log=on 共 0 行受到影响 执行耗时 : 0.008 sec传送工夫 : 0 sec总耗时 : 0.008 sec 3. 查看慢查问日志存哪-- 查看慢查问日志存在哪里 show variables like '%slow_query_log%';Variable_name Valueslow_query_log ONslow_query_log_file /var/lib/mysql/niewj-slow.log4. 多慢才算慢查问show variables like 'long_query_time';+-----------------+-----------+| Variable_name | Value |+-----------------+-----------+| long_query_time | 10.000000 |+-----------------+-----------+默认是10秒, 试验时能够改成2s: set global long_query_time=2;留神换个session能力查到(须要从新进入) 从新输出用户名明码: mysql> show variables like 'long_query_time';+-----------------+----------+| Variable_name | Value |+-----------------+----------+| long_query_time | 2.000000 |+-----------------+----------+1 row in set (0.00 sec)5. 模仿慢查问看会否进入慢查问日志:select sleep(11);tail -f /var/lib/mysql/niewj-slow.log ...

September 10, 2020 · 1 min · jiezi

关于mysql优化:数据库优化-02

上文中曾经在主库/从库都装置了数据库,接下来进行挂载原理; 数据库主从配置1.数据库挂载配置1.查看主数据库状态:默认条件下数据库是不能充当主库的,咱们须要自行去开启二进制sqlyog命令:show master status;2.开启数据库二进制文件:批改主库外围配置文件--my.cnf-->vim /etc/my.cnf-->增加服务ID号sever-id=1/数据库日志文件log-bin=mysql-bin/(datedir为生成的二进制文件的地位)3.重启mysql数据库:当批改好my.cnf文件之后,须要重启数据库,查看二进制日志文件是否存在cd /var/lib/mysql/进入mysql目录-->systemctl restart mariadb重启数据库-->ls查看是否有mysql-bin.000001二进制文件4.再次查看数据库状态:5.批改从库二进制文件:vim /etc/my.cnf-->增加服务ID号sever-id=2(此处要与主库辨别)/数据库日志文件log-bin=mysql-bin-->重启数据库systemctl restart mariadb-->查看库状态 2.实现主从库挂载1.挂载剖析:从库挂载主库 主库IP地址/主库端口号/登录用户名/登录的明码/二进制日志文件/读取地位 这些都须要用到.2.挂载实现:SQLYog代码:留神分段选中执行 /*我是从库信息 130*/ SHOW MASTER STATUS; /*实现主从的挂载*/ CHANGE MASTER TO MASTER_HOST="192.168.126.129", MASTER_PORT=3306, MASTER_USER="root", MASTER_PASSWORD="root", MASTER_LOG_FILE="mysql-bin.000001", MASTER_LOG_POS=245/*启动主从服务*/ START SLAVE /*查看主从的状态*/ SHOW SLAVE STATUS /*如果报错 则重新配置*/ STOP SLAVE查看主从状态显示两个YES则示意配置正确3.主从同步测试:批改主库中的数据,查看从库数据是否同步即可 3.Mycat介绍mycat--数据库分库分表中间件(阿里) 4.实现数据库读写拆散机制1.原理:通过代理数据库将写操作交由主库,读操作交由从库,主从库还会进行挂载2.上传mycat服务:将tar.gz压缩包拖至MobaXterm中src目录下-->tar -xvf命令解压3.编辑配置文件-server.xml:用户与代理数据库的链接通过server.xml文件进行配置. IP/PORT/USER/PASSWORD <!--配置默认端口号--><property name="serverPort">8066</property> <!--用户标签--> <user name="root"> <property name="password">root</property> <!--与schema.xml中的配置雷同 留神数据库的大小写--> <property name="schemas">jtdb</property> </user> <user name="user"> <property name="password">user</property> <property name="schemas">jtdb</property> <property name="readOnly">true</property> </user>4.编辑配置文件-schema.xml: <?xml version="1.0"?><!DOCTYPE mycat:schema SYSTEM "schema.dtd"><mycat:schema xmlns:mycat="http://io.mycat/"> <!--name属性是自定义的 dataNode示意数据库的节点信息 jtdb示意逻辑库--> <schema name="jtdb" checkSQLschema="false" sqlMaxLimit="100" dataNode="jtdb"/> <!--定义节点名称/节点主机/数据名称--> <dataNode name="jtdb" dataHost="localhost1" database="jtdb" /> <!--参数介绍 UTF-8 中文报错--> <!--balance 0示意所有的读操作都会发往writeHost主机 --> <!--1示意所有的读操作发往readHost和闲置的主节点中--> <!--writeType=0 所有的写操作都发往第一个writeHost主机--> <!--writeType=1 所有的写操作随机发往writeHost中--> <!--dbType 示意数据库类型 mysql/oracle--> <!--dbDriver="native" 固定参数 不变--> <!--switchType=-1 示意不主动切换, 主机宕机后不会主动切换从节点--> <!--switchType=1 示意会主动切换(默认值)如果第一个主节点宕机后,Mycat会进行3次心跳检测,如果3次都没有响应,则会主动切换到第二个主节点--> <!--并且会更新/conf/dnindex.properties文件的主节点信息 localhost1=0 示意第一个节点.该文件不要随便批改否则会呈现大问题--> <dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100"> <heartbeat>select 1</heartbeat><!--心跳检测--> <!--配置第一台主机次要进行写库操作,在默认的条件下Mycat次要操作第一台主机在第一台主机中曾经实现了读写拆散.因为默认写操作会发往137的数据库.读的操作默认发往141.如果从节点比较忙,则主节点分担局部压力. --> <writeHost host="hostM1" url="192.168.126.129:3306" user="root" password="root"> <!--读数据库1--> <readHost host="hostS1" url="192.168.126.130:3306" user="root" password="root" /> <!--读数据库2--> <readHost host="hostS2" url="192.168.126.129:3306" user="root" password="root" /> </writeHost> <!--定义第二台主机 因为数据库外部曾经实现了双机热备.--> <!--Mycat实现高可用.当第一个主机137宕机后.mycat会主动收回心跳检测.检测3次.--> <!--如果主机137没有给Mycat响应则判断主机死亡.则回启东第二台主机持续为用户提供服务.--> <!--如果137主机复原之后则处于期待状态.如果141宕机则137再次继续为用户提供服务.--> <!--前提:实现双机热备.--> <!--<writeHost host="hostM2" url="192.168.126.130:3306" user="root" password="root"> <readHost host="hostS1" url="192.168.126.130:3306" user="root" password="root" /> <readHost host="hostS2" url="192.168.126.129:3306" user="root" password="root" /> </writeHost>--> </dataHost></mycat:schema>5.替换/usr/local/src/mycat/conf目录下的server.xml以及schema.xml文件 ...

September 9, 2020 · 2 min · jiezi

关于mysql优化:数据库优化

数据库备份数据库备份是应用数据库必不可少的一环,能够对咱们不小心的操作及时做出补救.数据库备份分为以下两种. 数据库冷备份定义:定期将数据库中的数据实现转储.毛病: 1.数据库冷备份由人工操作,十分的不不便 2.数据库冷备份因为是定期执行,所以可能会导致数据失落. 当初的网络环境异常简单.数据库冷备份是复原数据的最初的无效伎俩. 数据库热备份定义:以实现数据库实时的备份,保证数据尽可能不失落.原理:原理剖析:1.当数据库主库执行更新操作时,会将更新的内容写入到二进制日志文件中. 并且写入二进制文件是一个异步的过程. 2.从库会开启IO线程去读取主库的二进制日志文件,之后写入中继(长期存储)日志中. 3.从库会开启SQL线程去读取中继日志中的信息.之后将数据同步到从库中. 上述的操作是由从库向主库获取数据, 所以从库实践上能够配置无数个(个别2-3台即可); 从库装置数据库为了实现数据库的热备份,咱们就须要筹备一个从库,那咱们就须要先筹备一个新的虚拟机,并在虚拟机中装置数据库 装置虚拟机不再赘述. 装置数据库依照上一篇文章中的步骤装置数据库 注意事项因为要实现数据库的主从的同步,所以必须保障数据库的信息统一. 步骤: 1.将主库的信息应用冷备份的形式导出.2.在数据库的从库中导入对应sql文件即可.

September 8, 2020 · 1 min · jiezi

关于mysql优化:wgcloud怎么监控mysql

首先找到监控主集的mysql的过程id号 而后在过程治理菜单,点击关上,增加 输出对应的过程 保留实现后,稍等一会就能看到mysql的内存和cpu信息了

July 20, 2020 · 1 min · jiezi

MySQL优化查阅总结

笔者实习小白,公司让我优化一下数据库,这几天复习了一下数据库的相干常识并且查阅了一些材料,筹备过几天把高性能MySQL看一下。MYSQL优化的次要形式之一:减少索引一、索引:1.定义: 是一种数据结构。(残缺定义:数据自身之外,数据库还保护着一个满足特定查找算法的数据结构,这些数据结构以某种形式指向数据,这样就能够在这些数据的根底上实现高级查找算法,这种数据结构就是索引) 2.分类: BTREE索引(B+树是一个均衡的多叉树,从根节点到每个叶子节点的高度差值不超过1,而且同层级的节点间有指针互相链接。)在B+树上的惯例检索,从根节点到叶子节点的搜寻效率根本相当,不会呈现大幅稳定,而且基于索引的程序扫描时,也能够利用双向指针疾速左右挪动,效率十分高。因而,B+树索引被广泛应用于数据库、文件系统等场景。 <center>图1 B+树</center>哈希索引:只须要做等值比拟查问,而不蕴含排序或范畴查问的需要,适宜应用哈希索引。比方对于身份证号的匹配。哈希索引定义:哈希索引基于哈希表实现,只有准确匹配索引的所有列的查问才无效。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码,哈希码是一个较小的值,并且不同键值的行计算出来的哈希码也不一样。哈希索引将所有的哈希码存储在索引中,同时在哈希表中保留指向每个数据行的指针。 <center>图2 哈希构造</center> 3.索引劣势:不用检索全表,进步检索效率,(应用相似二分查找的形式)升高数据库的IO老本;升高数据排序的老本,升高了CPU的耗费。 4.索引劣势:索引也是占空间的;尽管进步了查问速度,然而会升高表的更新速度,在表更新时,也会对索引更新; 5.须要索引的状况:主键主动建设惟一索引;频繁查找的字段;查问中与其余表关联的字段,通过外键关系建设索引;频繁更新的字段不要建设索引;where条件里用不到的不要建设索引; 二、MYSQL优化查问语句EXPLAIN: MySQL 提供了一个 EXPLAIN 命令, 它能够对 SELECT 语句进行剖析, 并输入 SELECT 执行的详细信息, 以供开发人员针对性优化.EXPLAIN 命令用法为:在 SELECT 语句前加上 EXPLAIN,例如: <center>图2 EXPLAIN利用</center> EXPLAIN进去的信息有10列,别离是id、select_type、table、type、possible_keys、key、key_len、ref、rows、Extra,上面对这些字段呈现的可能进行解释: 1.id 它是SQL执行的程序的标识,从大到小的执行2.select_type示意查问中每个select子句的类型(1) SIMPLE(简略SELECT,不应用UNION或子查问等)(2) PRIMARY(查问中若蕴含任何简单的子局部,最外层的select被标记为PRIMARY)(3) UNION(UNION中的第二个或前面的SELECT语句)(4) DEPENDENT UNION(UNION中的第二个或前面的SELECT语句,取决于里面的查问)(5) UNION RESULT(UNION的后果)(6) SUBQUERY(子查问中的第一个SELECT)(7) DEPENDENT SUBQUERY(子查问中的第一个SELECT,取决于里面的查问)(8) DERIVED(派生表的SELECT, FROM子句的子查问)(9) UNCACHEABLE SUBQUERY(一个子查问的后果不能被缓存,必须从新评估外链接的第一行) 3.table显示这一行的数据是对于哪张表的,有时不是实在表名字,subquery示意子查问的表。 4.type示意MySQL在表中找到所需行的形式,又称“拜访类型”。罕用的类型有: ALL, index, range, ref, eq_ref, const, system, NULL(从左到右,性能从差到好)ALL:Full Table Scan, MySQL将遍历全表以找到匹配的行index: Full Index Scan,index与ALL区别为index类型只遍历索引树range:只检索给定范畴的行,应用一个索引来抉择行ref: 示意上述表的连贯匹配条件,即哪些列或常量被用于查找索引列上的值eq_ref: 相似ref,区别就在应用的索引是惟一索引,对于每个索引键值,表中只有一条记录匹配,简略来说,就是多表连贯中应用primary key或者 unique key作为关联条件const、system: 当MySQL对查问某局部进行优化,并转换为一个常量时,应用这些类型拜访。如将主键置于where列表中,MySQL就能将该查问转换为一个常量,system是const类型的特例,当查问的表只有一行的状况下,应用system。NULL: MySQL在优化过程中合成语句,执行时甚至不必拜访表或索引,例如从一个索引列里选取最小值能够通过独自索引查找实现。 ...

July 13, 2020 · 2 min · jiezi

SELECT-效率低面试官为什么

为什么大家都说SELECT * 效率低 作者 : 陈哈哈来源: https://urlify.cn/ZvM3qe 面试官:“小陈,说一下你常用的SQL优化方式吧。” 陈小哈:“那很多啊,比如不要用SELECT *,查询效率低。巴拉巴拉...” 面试官:“为什么不要用SELECT * ?它在哪些情况下效率低呢?”陈小哈:“SELECT * 它好像比写指定列名多一次全表查询吧,还多查了一些无用的字段。” 面试官:“嗯...”陈小哈:“emmm~ 没了” 陈小哈:“....??(几个意思)” 面试官:“嗯...好,那你还有什么要问我的么?”陈小哈:“我问你个锤子,把老子简历还我!” 无论在工作还是面试中,关于SQL中不要用“SELECT *”,都是大家听烂了的问题,虽说听烂了,但普遍理解还是在很浅的层面,并没有多少人去追根究底,探究其原理。 废话不多说,本文带你深入了解一下"SELECT * "效率低的原因及场景。 本文很干!请自备茶水,没时间看记得先收藏 -- 来自一位被技术经理毒打多年的程序员的忠告一、效率低的原因先看一下最新《阿里java开发手册(泰山版)》中 MySQL 部分描述: 4 - 1. 【强制】在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明。 说明: 增加查询分析器解析成本。 增减字段容易与 resultMap 配置不一致。 无用字段增加网络 消耗,尤其是 text 类型的字段。开发手册中比较概括的提到了几点原因,让我们深入一些看看: 1. 不需要的列会增加数据传输时间和网络开销用“SELECT * ”数据库需要解析更多的对象、字段、权限、属性等相关内容,在 SQL 语句复杂,硬解析较多的情况下,会对数据库造成沉重的负担。 增大网络开销;* 有时会误带上如log、IconMD5之类的无用且大文本字段,数据传输size会几何增涨。如果DB和应用程序不在同一台机器,这种开销非常明显 即使 mysql 服务器和客户端是在同一台机器上,使用的协议还是 tcp,通信也是需要额外的时间。 2. 对于无用的大字段,如 varchar、blob、text,会增加 io 操作准确来说,长度超过 728 字节的时候,会先把超出的数据序列化到另外一个地方,因此读取这条记录会增加一次 io 操作。(MySQL InnoDB) ...

July 3, 2020 · 1 min · jiezi

Mysql索引数据结构详解及性能调优

以前学习了不少东西,都忘了不少,最近就想着总结一下,就想到想写博客文章来总结下自己这些年学习的东西,记录下各方面技术学习情况。 如果觉得好看,请给个赞 你有一个思想,我有一个思想,我们交换后,一个人就有两个思想If you can NOT explain it simply, you do NOT understand it well enough简单介绍下这篇文章的流程 1.为什么使用索引A:那还用说,肯定在某些方面有特长呗,比如你知道的【它很快,非常快】 我也很赞同这个答案,但说的不够具体,你得说明它为啥这么快 如果问你选择索引的原因就是一个【快】字,面试也就不会出那么多幺蛾子了。你有没有问过你自己 索引在所有场景下都是快的吗?知道它很快,何为快?怎样度量?索引(翻译官方文档)是帮助MySQL高效获取数据的排好序的数据结构拿汉语字典的目录页(索引)打比方,我们可以按拼音、笔画、偏旁部首等排序的目录(索引)快速查找到需要的字。 实际上,索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录。 2.索引数据结构详解在创建索引时,通常采用的数据结构有:Hash、二叉树、红黑树、B树以及B+树 可以在线查看数据结构的网页 Data Structure 二叉树:定义规则为左边节点值比根节点小,右边节点值比根节点大,并且左右子节点都是排序树 要是索引采取这种结构,数值递增那种,就要满足右边节点值比根节点大,导致检索数据会导致查了6遍磁盘 红黑树:(在jdk8之后,用数组+链表+红黑树来实现hashmap,当碰撞的元素个数大于8时 & 总容量大于64,会有红黑树的引入。)红黑树是一种自平衡二叉树,主要解决二叉搜索树在极端情况下退化成链表的情况,在数据插入的时候同时调整整个树,使其节点尽量均匀分布,保证平衡性,目的在于降低树的高度,提高查询效率。(右边的树的高度不会大于左边树的高度超过1,大于等于1级后会自动平衡,自己可在数据结构上插入试试) 特点: 节点是红色或者黑色根节点是黑色每个叶子的节点都是黑色的空节点(NULL)每个红色节点的两个子节点都是黑色的从任意节点到其每个叶子的所有路径都包含相同的黑色节点 优点:解决二叉搜索树的极端情况的退化问题。缺点:检索时间依旧与树的高度有关,当数据量很大时,树的高度就会很高,检索的次数就会比较多,检索的时间会比较久,效率低。从前面分析情况来看,减少磁盘IO的次数就必须要压缩树的高度,让瘦高的树尽量变成矮胖的树,所以B-Tree就在这样伟大的时代背景下诞生了 B-Tree 基于以上进行扩容,每个横向的节点变多了意味的存放的数据变多了,整个树的高度也变小了,减少磁盘io的搜索速度 特点1.叶节点具有相同的深度,叶节点的指针为空2.所有索引元素不重复3.节点中的数据索引从左到右递增排列 缺点:可以看到存放的数据类似key+value 的数据 要是InnoDB 的话data可能存放的是除了索引外的字段 页节点mysql默认推荐的是16k大小 ( show global status like 'Innodb_page_size';),假如大节点的每个节点的data存的数据比较大,那么意味着横向能存储的索引就会变很少,大节点的能存储的索引变少意味着整颗树的高度受到限制 B+Tree(B-Tree变种 MySql默认使用索引结构) 1.非叶子节点不存储data,只存储索引(冗余),可以放更多的索引2.叶子节点包含所有索引字段3.叶子节点用指针连接,提高区间访问的性能 (快速定位范围查询,例如查询大于20,第一次io从根节点查询三次定位到20,然后通过后面的指针查询大于20的数据,就不用再从根节点的重新再查询,提高性能,叶子节点开始结束节点也是用指针连接串起来的) Hash前面说的mysql默认索引结构是B+Tree,还有一种索引结构是Hash如果是hash 的话是通过 hash(值)运算然后在磁盘中快速查找对应的磁盘文件指针从而找到行数据hash 索引查数据是很方便也快的,但是不支持范围性查找 例如 >= < between and 也不支持排序Hash索引适合等值查询 ,不适合范围查询 总结为什么mysql索引结构默认使用B+Tree为什么mysql索引结构默认使用B+Tree,而不是Hash,二叉树,红黑树?B-tree:因为B树不管叶子节点还是非叶子节点,都会保存数据,这样导致在非叶子节点中能保存的指针数量变少(有些资料也称为扇出),指针少的情况下要保存大量数据,只能增加树的高度,导致IO操作变多,查询性能变低;Hash:虽然可以快速定位,但是没有顺序,IO复杂度高。二叉树:树的高度不均匀,不能自平衡,查找效率跟数据有关(树的高度),并且IO代价高。红黑树:树的高度随着数据量增加而增加,IO代价高。 索引是如何支持千万级表的快速查询索引可以把它想象跟旁边的指针的成对存在的(指针是指向下一个节点的磁盘位置(占用6字节))索引假设字段为数字类型 Bigint 8b+ 指针默认占用空间6b = 14b (索引跟旁边的指针的成对存在的大小总和)大节点能存放 16kb数据 那么最多能存放 16kb * 1024/ 14= 1170个索引  ...

July 2, 2020 · 11 min · jiezi

数据库表被锁的原因分析和数据库乱码解决

一、记录一次准备给客户预演示出现的问题 事故的背景:当所以功能开发完成后,开发人员在本地进行了测视已经没问题了。就把所有开发的功能模块合并到 dev 分支,进行打包,发布到预演示的线上环境。当在给相关人员进行演示的时候,出现了问题。 我们使用 https 调用对方的接口发送 Json 数据,对方进行校验马上返回校验的响应结果。问题出现在我们每次发送数据都是成功的,但是对方发送回来的数据,一直不能正常插入 DB(使用的是 Oracle)。 事故的真正原因: 因为有个同事在进行了 delete 后没有进行 commit 提交。导致表一直被锁住,不能被其他人使用。 但是杀死进程和本地 commit 几次后,依旧无法插入数据,提示还是 DB 被这个同事锁住。 最后查出的真正原因是,这个同事首先使用了 无线网络 进行了 DB 的操作,当时并没有 commit 提交操作。而后又插上网线继续工作,问题就出现在这里。 首先,当我们使用无线网络操作 DB 时,Oracle 会默认这是一次会话(session),当开发人员对 DB 进行操作后(没有 commit ),又切换到了有线网络状态下,而这 2 种状态下的本机 IP 是不一样的(有兴趣的朋友可以试试使用无线和有线连接状态下,同一台电脑的 IP 地址是否一样)。 Oracle 会认为第一次 session 没有关闭,会将表锁住,等待这次 session 会话的提交直到结束。 所以当同事再连接上网线,使用有线网络进行 commit 时候会提示操作失败。因为 Oracle 数据库认为这又是一次新的 session 会话。而第一次的 session 会话并没有结束,就会导致出现了 DB 一直被锁的情况。 事故的解决办法: 首先使用语句查询(只有 Admin 用户才可以)出被锁住的表和锁表的开发人员的工号。 然后杀死这个进程或者进行 DB 重启即可。 ...

November 4, 2019 · 1 min · jiezi

8SQL语句优化四之MySQL数据库慢查询问题排查

参考原文:MySQL数据库慢查询问题排查方法0.准备 show variables like '%log_output%';-- log_output='FILE|TABLE' 默认是保存到文件中show variables like '%slow_query_log%'; #查看当前开启状态-- slow_query_log_file:/usr/local/mysql/var/cffycls-PC-slow.log #在配置文件中修改或添加,并重启服务slow_query_log 1 #开启慢查询日志 set GLOBAL slow_query_log=1# 保存为table类型时,可以查询到信息字段类型:DESC `mysql`.`slow_log`fieldtypenullkeydefaultextrastart_timetimestamp(6)NOCURRENT_TIMESTAMP(6) DEFAULT_GENERATED on update CURRENT_TIMESTAMP(6)user_hostmediumtextNO query_timetime(6)NO lock_timetime(6)NO rows_sentint(11)NO rows_examinedint(11)NO dbvarchar(512)NO last_insert_idint(11)NO insert_idint(11)NO server_idint(10) unsignedNO sql_textmediumblobNO thread_idbigint(21) unsignedNO 日志文件 [8.0.13默认没有开启slow_query_log=1时,保存的慢操作日志] /usr/local/mysql/bin/mysqld, Version: 8.0.13 (Source distribution). started with:Tcp port: 3306 Unix socket: /tmp/mysql.sockTime Id Command Argument# Time: 2019-08-26T00:06:31.481150Z# User@Host: root[root] @ localhost [] Id: 174# Query_time: 17.501530 Lock_time: 0.000000 Rows_sent: 0 Rows_examined: 0use optmize;SET timestamp=1566777991;call add_vote_record_memory(10000);# Time: 2019-08-26T00:09:48.696806Z# User@Host: root[root] @ localhost [] Id: 174# Query_time: 126.785068 Lock_time: 0.000000 Rows_sent: 0 Rows_examined: 0SET timestamp=1566778188;call add_vote_record_memory(80000);# Time: 2019-08-26T00:42:43.720150Z# User@Host: root[root] @ localhost [] Id: 174# Query_time: 123.697077 Lock_time: 0.000000 Rows_sent: 0 Rows_examined: 0SET timestamp=1566780163;CALL add_vote_record_memory(100000);# Time: 2019-08-26T01:08:11.645078Z# User@Host: root[root] @ localhost [] Id: 174# Query_time: 1001.577474 Lock_time: 0.000000 Rows_sent: 0 Rows_examined: 0SET timestamp=1566781691;CALL add_vote_record_memory(800000);/usr/local/mysql/bin/mysqld, Version: 8.0.13 (Source distribution). started with:Tcp port: 3306 Unix socket: /tmp/mysql.sockTime Id Command Argumentroot@cffycls-PC:/home/wwwroot/default# cat /usr/local/mysql/var/cffycls-PC-slow.log/usr/local/mysql/bin/mysqld, Version: 8.0.13 (Source distribution). started with:Tcp port: 3306 Unix socket: /tmp/mysql.sockTime Id Command Argument# Time: 2019-08-26T00:06:31.481150Z# User@Host: root[root] @ localhost [] Id: 174# Query_time: 17.501530 Lock_time: 0.000000 Rows_sent: 0 Rows_examined: 0use optmize;SET timestamp=1566777991;call add_vote_record_memory(10000);# Time: 2019-08-26T00:09:48.696806Z# User@Host: root[root] @ localhost [] Id: 174# Query_time: 126.785068 Lock_time: 0.000000 Rows_sent: 0 Rows_examined: 0SET timestamp=1566778188;call add_vote_record_memory(80000);# Time: 2019-08-26T00:42:43.720150Z# User@Host: root[root] @ localhost [] Id: 174# Query_time: 123.697077 Lock_time: 0.000000 Rows_sent: 0 Rows_examined: 0SET timestamp=1566780163;CALL add_vote_record_memory(100000);# Time: 2019-08-26T01:08:11.645078Z# User@Host: root[root] @ localhost [] Id: 174# Query_time: 1001.577474 Lock_time: 0.000000 Rows_sent: 0 Rows_examined: 0SET timestamp=1566781691;CALL add_vote_record_memory(800000);/usr/local/mysql/bin/mysqld, Version: 8.0.13 (Source distribution). started with:Tcp port: 3306 Unix socket: /tmp/mysql.sockTime Id Command Argument# Time: 2019-10-08T00:59:30.702335Z# User@Host: root[root] @ [192.168.1.102] Id: 13# Query_time: 17.960220 Lock_time: 0.000272 Rows_sent: 0 Rows_examined: 0use optmize;SET timestamp=1570496370;call add_vote_record_memory(10000);a.直接杀进程 ...

October 8, 2019 · 2 min · jiezi

关于Mysql-大型SQL文件快速恢复方案

在使用Mysql数据库的过程中,经常需要使用到备份和恢复数据库,最简单便捷的方法便是通过导出SQL数据文件和导入SQL数据文件来完成备份和恢复,但是随着项目的增长,数据量越来越大,每次恢复就成了一件很头疼的事情。 当我最近一次拉下项目中的5GB大小的数据库到本地进行恢复时,竟然需要耗时40-50分钟,想着日后的数据扩增,数据量越来越大,恢复成本也越来越高,于是便查阅了一些资料,可以通过以下设置来提高你的恢复效率. 1.更改备份参数首先我们需要在备份数据库的时候,可以通过更改参数来提高我们的恢复效率. mysqldump --extended-insertmysqldump的--extended-insert参数表示批量插入,会将多个insert语句合并成一个语句,与没有开启-extended-insert的备份导入效率相差3-4倍. 使用--extended-insert=false导出的sql文件数据是这样的,每行一条insert语句,执行效率非常低下 使用--extended-insert=true导出的表如下图这种,一个很长的insert语句,会进行批量插入。 2.调整MYSQL快速插入参数如果你的数据库储存引擎是MYISAM参数的话,可以将此参数设置到512M或256M,MyISAM会使用一种特殊的树状缓存来做出更快的批量插入。 相关文档https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_bulk_insert_buffer_size 该值默认是8M = 8388608byte 查看插入缓冲区大小 SHOW VARIABLES LIKE '%bulk%';设置插入缓冲区大小(全局) SET GLOBAL bulk_insert_buffer_size =1024*1024*512;设置插入缓冲区大小(session) SET bulk_insert_buffer_size =1024*1024*256;如果需要设置Mysql重新启动时,依然保留该值,需要将这段配置添加了my.cnf [mysqld]bulk_insert_buffer_size = 256M3.关闭检查项对于Innodb引擎中,我们可以关闭一些系统检查项来实现更快的插入的方案. //关闭自动提交SET autocommit=0;//关闭唯一检查set unique_checks = 0;//关闭外键检查SET foreign_key_checks=0;//备份的时候开启--extended-insert参数关于Innodb批量数据加载相关文档:https://dev.mysql.com/doc/refman/5.7/en/optimizing-innodb-bulk-data-loading.html 4.实践做好以上优化后,你的Mysql恢复效率瞬间会提升一个档次,在没做以上参数优化时,每次恢复数据库都需要耗费40分钟的时间,设置后只需要16分钟左右,我的数据库文件容量在5GB左右. 以上就这些,途观有更好的方案和建议的话,希望各位同学一起探讨,Happy Coding。

September 10, 2019 · 1 min · jiezi

Mysql索引优化一索引类型索引策略

上一篇已经讲了索引的基本类型,这一篇主要介绍下如何选择更高效的索引类型。 独立的列现在有下面一张学生成绩表 CREATE TABLE `student` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `first_name` varchar(20) NOT NULL, `last_name` varchar(20) NOT NULL, `created_at` timestamp NOT NULL, `updated_at` timestamp NOT NULL, `score` int(3) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `created_at` (`created_at`) KEY `score` (`score`)) ENGINE=InnoDB DEFAULT CHARSET=utf8现在我们要根据学生成绩查询学生姓名,这是一个很简单的查询。select first_name,last_name from student where score=99;这条sql就使用到了索引score。但是我们通常会看到很多查询不恰当的使用到索引,最后就导致mysql没办法使用到索引。如果查询中的不是独立的,则Mysql不会使用到索引,独立的列是指索引列不能是表达式的一部分,也不能是函数的参数。如select first_name,last_name from student where score+1=100;这个查询是不能使用到索引的。再如:select first_name from student where TO_DAYS(NOW())-TO_DAYS(created_at)>0;也是不能使用到索引的。 前缀索引有时候需要索引的列是很长的字符串,如果直接创建索引会使索引变的很大这样就变的比较慢了。改进方式现在能想到就是前面说到的哈希索引,但是有时候哈希索引是不适合的。这个时候就有了前缀索引:把列开始的部分字符串作为索引,这样可以大大的节约索引空间,从而提高索引效率。但这样也会降低索引的选择性。索引选择性指:不重复的索引值和数据表总数的比值。索引的选择性越高,那么索引的查询效率越高。唯一索引的选择性最高为1,性能也是最好的。对于很长的VARCHAR,TEXT这样的列,如果要作为索引的话,那么必须使用前缀索引。那么怎么选择合适的前缀索引呢。诀窍在于要选择足够长的前缀以保证比较高的索引选择性,同时又不能太长,因为索引越短,索引空间越小。如:一张订单表,要为联系人手机号做前缀索引,这个适合需要分析多少长度的前缀索引,可以查询每个长度的 SELECT COUNT(DISTINCT LEFT(phone,3))/COUNT(*) AS pre3,COUNT(DISTINCT LEFT(phone,4))/COUNT(*) AS pre4,COUNT(DISTINCT LEFT(phone,5))/COUNT(*) AS pre5,COUNT(DISTINCT LEFT(phone,6))/COUNT(*) AS pre6,COUNT(DISTINCT LEFT(phone,7))/COUNT(*) AS pre7,COUNT(DISTINCT LEFT(phone,8))/COUNT(*) AS pre8 FROM orders;+--------+--------+--------+--------+--------+--------+| pre3 | pre4 | pre5 | pre6 | pre7 | pre8 |+--------+--------+--------+--------+--------+--------+| 0.0026 | 0.0216 | 0.1397 | 0.3274 | 0.4533 | 0.4533 |+--------+--------+--------+--------+--------+--------+1 row in set (0.10 sec)可以看到在长度为7的时候,选择性的提升已经很小了。这个时候,我们就可以考虑取7这个值了。当然这里只是一个举例,在实际场景中,手机号的长度完全可以直接作为普通索引的。前缀索引是比较小而且快,但是Mysql不能用前缀索引作为group by 和order by,也不能做覆盖扫描(所以查询必须回表)。 ...

July 15, 2019 · 2 min · jiezi

mysql高级使用和技巧

文章来源:www.liangsonghua.me作者介绍:京东资深工程师-梁松华,长期关注稳定性保障、敏捷开发、JAVA高级、微服务架构 一、普通索引和唯一索引查询上来说,普通索引查找到满足条件的记录后会接着查找下一个记录(innodb的数据是按页读写的),判断是否满足。然而唯一索引是查询到了就立即返回了。所以如果你明确知道只有一条结果则应该加上limit 1 更新上来说,普通索引会用到charge buffer优化,将更新操作记录在charge buffer中,不需要从磁盘中读取数据然后再更新,当下次查询该数据页时再读入内存然后执行merge相关操作,更新原数据。 二、前缀索引查询上来说,前缀索引可能会导致在索引树上命中率变高但是原数据命中并不一定高,造成了一定的查询浪费。另外对于索引上的信息足够满足查询条件的情况下,前缀索引会多一次回表操作,整体索引则是直接返回(也就是覆盖索引)。 但是如果提高数据的区分度,比如倒序存储、hash处理后存储等,使用前缀索引存储空间更小,查询次数也不会太差,收益可能会更高。 三、联合索引对于联合索引来说,遵守最左前缀原则,也就是说如果只有idx-union(type,time,value)联合索引,单纯的type或者type and time作来查询条件也会命中这条索引,但是单纯value作为查询条件则无法命中。另外如果存在范围查询比如between等也会导致无法命中 四、收缩表空间当需要收缩表空间时,如果只是delete数据,表文件大小是不变的,会被mysql标记为可复用的空间,需要通过alter重建表才能释放。当然如果是要删除全部数据的话,首选应该是Truncate操作。 五、count(*)操作InnoDb是索引组织表,主键索引树的叶子节点存的是整行数据,而普通索引树的叶子节点是主键值(需要先查找k索引树得到ID,然后再到ID索引树查找,也就是回表),不管是优化器查询哪个索引树或者不使用索引,都需要将所有数据查出来然后累加返回,所以不推荐在innodb引擎的数据库中频繁执行count(*)操作。 六、显示随机信息如果使用order by rand()实现,则需要在临时表上进行rowid(有主键则是主键没有则是系统生成标识行的rowid)排序操作,整体过程涉及全表扫描然后将数据放到内存临时表再生成sort_buffer排序再从内存临时表中取数据。如果sort_buffer_size无法存储数据,则需要使用磁盘文件进行分块存储然后再归并排序。 正确的方式应该是: mysql> select count(*) into @C from t;set @Y1 = floor(@C * rand());set @Y2 = floor(@C * rand());set @Y3 = floor(@C * rand());select * from t limit @Y1,1; // 在应用代码里面取 Y1、Y2、Y3 值,拼出 SQL 后执行select * from t limit @Y2,1;select * from t limit @Y3,1; 七、where条件上不要使用函数对索引字段做函数操作,可能会破坏索引值的有序性,因为b+树中的同一层兄弟节点是有序的 八、where条件不要使用类型转换当字符串和数字做比较时,会将字符串转换成数字,会触发CAST等函数操作,触发上一条规则 九、数据库用错索引时可以强制force index我们知道Mysql结合扫描行数、是否使用临时表、是否需要排序等综合考虑选择索引,当然就会出现用错索引的情况。平时我们explain sql时显示的预估扫描行数rows是mysql通过数据采样,选择这个索引其中n个数据页,统计这些页面的不同值,得到平均值,然后乘以这个索引的页面数,得到基数,也就是区分度。另外还有如果回表代价过大,也可能会选错索引。 十、join操作join操作的过程是先遍历表t1,然后根据从表t1取中的数据到表t2中查找满足条件的记录,也就是说驱动表是走全表扫描,而被驱动表是走树查找(index nested-loop join)。那么理所当然选择小表(过滤后)作为驱动表效果更好。 ...

July 11, 2019 · 1 min · jiezi

Mysql索引优化一索引类型

索引对于良好的性能非常关键,尤其是在数据量越来越大的时候。恰当的索引对性能的帮助是非常巨大的,不恰当的索引不禁不能对性提升有帮助,当数据量达到一定级别的时候还可能造成性能的下降。所以了解索引对Mysql性能优化有着至关重要的作用。Mysql索引基本类型有 B-Tree,哈希索引,全文索引,空间数据索引(R-Tree)。其中B-Tree、哈希、全文索引是我们经常用到的。 B-Tree索引B-Tree索引是我们口中经常说的索引类型(有些存储引擎中使用的是B+Tree。如InnoDB)。每个引擎对于BTREE索引使用方式是不一样的。如 MyISAM引擎使用的是前置压缩技术,这样索引会变的很小。而InnoDB则是按照原有的数据格式来存储的。MyISAM索引是通过数据的物理位置来找到被索引的行,而InnoDB则是根据被索引的行的主键来找到被索引行的。B-Tree索引的所有值都是按顺序存储的,并且每个叶子节点到根节点的距离是相同的。下面给出一个简单的示意图 假设有下表: CREATE TABLE student( first_name varchar(20) not null, last_name varchar(20) not null, age tinyint(3) not null, created_at timestamp not null, key(first_name ,last_name));可以使用到B-Tree索引的查询 全值匹配 全值匹配指对索引中的所有列进行匹配。如查询姓名是 zhang san的人 select * from student where first_name='zhang' and last_name='san'; 这里使用了索引的第一列与第二列匹配最左前缀,如查询姓为张的人 select * from student where first_name='zhang' ;这里使用了索引的第一列匹配列前缀,也可以值匹配某一列的开头部分,如 select * from student where first_name='zha' ;这里使用了索引的第一列匹配范围值,如 select * from student where first_name>'bao' and first_name<'zhang';这样也会使用到索引的第一列只访问索引的查询,如果查询条件是 select first_name,last_name from student where first_name='zhang' ;那么查询就只会访问索引,而不会再去根据主键回表查询数据。这里需要注意的是;B-Tree 索引需要根据最左前缀查询,如果不是按照索引的最左列开始查询,那么是不会使用到索引的。例如:select * from student where last_name='san';select * from student where first_name like '%ha%'; 这样的sql是没办法命中索引的。对于第二条sql如果需要使用索引,那么应该改为 select * from student where first_name like 'ha%';哈希索引哈希索引是基于哈希表实现的,只有精确匹配索引所有列的查询才会使用到索引,只有Memory引擎才支持哈希索引。假设有下表: ...

July 3, 2019 · 1 min · jiezi

Mysql范式与数据类型选择

良好的逻辑设计与物理设计是高性能的基石,当我们在设计数据表结构的时候,应该跟根据业务逻辑来分析具体情况,然后设计出比较合理,高效的数据表结构在数据表结构设计中,不得不提的就是范式与数据类型了 Mysql三范式字段不可分;即字段具有原子性 字段不可再分,否则就不是关系数据库;有主键,非主键字段依赖主键; 唯一性 一个表只说明一个事物非主键字段不能相互依赖;每列都与主键有直接关系,不存在传递依赖范式的优点与缺点优点 范式化设计更新通常比反范式化更新要快当数据高度范式化时,就只有少量或者没有重复数据,这样修改的时候就只需要修改少量的数据范式化的表通常比较小,可以更好的放在内存里面,操作就会更容易因为范式化设计之后,冗余数据较少,所以在执行某些查询的时候可能就不会用到group by,distinct 这样也提高了查询效率缺点因为严格遵循范式化设计的话。在某些业务场景下可能会查询多个表。这样的同样会使得查询效率变的很低。而且在某些时候因为多表查询的原因,可能某些索引不会被命中。这里我们看到范式化的设计有优点也有缺点,所以在实际的项目中,我们通常是混范式化设计。某些表完全遵循范式化;某些表遵循部分范式化设计。在设计某些表的时候 会用到反范式化的思想,将某些数据存到同一张表中。这样可以减少很多关联查询,也可以更好的去设计索引关系。 比如users表 与 user_messages表中,都会保存一个user_account_type字段。这样的话,在单独查询user_account_type=1的消息总数时就不需要再去关联users表了。数据类型Mysql支持的数据类型有很多种,所以选择正确的数据类型对提高性能有着至关重要的作用。但是不管哪种数据类型我们都应该参考下面几个原则 更小的通常更好;更小的数据类型通常更快,因为他们占用更少的磁盘、内存、CPU缓存。简单就好;操作简单的数据类型通常需要更少的CPU周期。例如:整型比字符串操作代价更低尽量避免NULL;如果查询中包含可为Null的列,对于Mysql来说更难优化,因为可以为null的列使得索引,索引统计和值都变的更加复杂整数类型Mysql的整数类型有 TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT。他们使用到的储存空间分别是,8,16,24,32,64位。值的范围是-2(N-1)到2(N-1)-1整形可以选择UNSIGNED属性,表示是否有符号,不允许为负值。如 TINYINT UNSIGNED 值的范围 0~255, 而 TINYINT 值的范围是-128-127。需要注意点是有符号跟无符号使用相同的储存空间,拥有相同的性能,所以可以根据实际情况来选择类型。 字符串类型VARCHAR,CHAR是两种最主要的字符串类型, VARCHAR类型用于储存可变字符串,是常见的字符串类型。它比定长类型更节省空间。CHAR定长字符串类型,分配固定长度的空间。在保存某些定长字符串时比VARCHAR更有优势、比如md5定长字符串,因为定长类型字符串不容易产生碎片。对于VARCHAR(5)和VARCHAR(100)储存hello的空间开销是一样的,那么是不是我们就可以定义长度为100呢?当然不是了,更长的列会消耗更多的内存,因为Mysql通常会分配固定大小的内存块来保存内部值。所以最好的策略就是分配合理的长度,这样就分配到真正需要的 空间。日期,时间类型DATETIME类型,能够保存1001-9999年,精度为秒,与时区无关。TIMESTAMP类型,保存了从1970-01-01午夜到现在的秒数,只使用了4个字节,只能表示1970-2038年。TIMESTAMP依赖于时区。TIMESTAMP在默认情况下,如果没有指定列的值,会把列的值设置为当前时间,在更新的时候也可以更新列的值为当前时间。通常情况下应该尽量使用TIMESTAMP,因为它比DATETIME空间效率更高,有时候我们会将Unix时间戳保存为整数值以表示当前时间,实际上并不会带来任何收益。 上面列举了几个常用的mysql类型,在实际使用中可以根据业务选择最优的方案。一般情况下遵循 更小的通常更好,简单就好 ,尽量避免NULL是没有问题的。关于mysql的数据类型选择,就写到这里。后面也会写一些关于索引优化方面的文章,如果问题欢迎大家指出。

June 29, 2019 · 1 min · jiezi

mysql高级知识总结

这篇文章的知识点来自于极客时间专栏<<MySQL实战45讲>>,本文持续更新。 索引索引的目的:提高查询效率。 常见索引模型:哈希表、有序数组、搜索树哈希表:键 - 值(key - value)对。哈希思路:把值放在数组里,用一个哈希函数把key换算成一个确定的位置,然后把value放在数组的这个位置哈希冲突(多个 key 值经过哈希函数的换算,会出现同一个值的情况)的处理办法:链表哈希表适用场景:只有等值查询的场景,比如 Memcached 及其他一些 NoSQL 引擎。 有序数组:按顺序存储。查询用二分法就可以快速查询,时间复杂度是:O(log(N))有序数组查询效率高,更新效率低,往中间插入一个记录就必须得挪动后面所有的记录,成本太高。有序数组的适用场景:静态存储引擎。 二叉搜索树:每个节点的左儿子小于父节点,父节点又小于右儿子二叉搜索树:查询时间复杂度O(log(N)),更新时间复杂度O(log(N))数据库存储大多不适用二叉树,因为树高过高,会适用N叉树 MySQL表引擎InnoDB中的索引模型:B+Tree索引类型:主键索引、非主键索引主键索引的叶子节点存的是整行的数据(聚簇索引),非主键索引的叶子节点内容是主键的值(二级索引) 主键索引和普通索引的区别:主键索引只要搜索ID这个B+Tree即可拿到数据。普通索引先搜索索引拿到主键值,再到主键索引树搜索一次(回表),所以在应用中尽量使用主键查询。 一个数据页满了,按照B+Tree算法,新增加一个数据页,叫做页分裂,会导致性能下降。空间利用率降低大概50%。当相邻的两个数据页利用率很低的时候会做数据页合并,合并的过程是分裂过程的逆过程。 自增主键使用自增主键的好处: 自增主键一般用整型做主键,则二级索引叶子节点只要 4 个字节;而如果使用业务字段做主键,比如字符串类型的身份证号,每个二级索引的叶子节点得占用约 20 个字节。插入数据时,自增主键能保证有序插入,避免了页分裂。从性能和存储空间方面考量,自增主键往往是更合理的选择。 使用业务字段做主键的使用场景: 只有一个索引该索引必须是唯一索引这就是典型的 KV 场景。由于没有其他索引,所以不用考虑其他索引的叶子节点大小的问题. 重建索引重建索引的目的:节省空间重建索引的办法:alter table T engine=InnoDB 联合索引覆盖索引:如果查询条件使用的是普通索引(或是联合索引的最左原则字段),查询结果是联合索引的字段或是主键,不用回表操作,直接返回结果,减少IO磁盘读写读取正行数据最左前缀:联合索引的最左 N 个字段,也可以是字符串索引的最左 M 个字符联合索引:根据创建联合索引的顺序,以最左原则进行where检索,比如(age,name)以age=1 或 age= 1 and name=‘张三’可以使用索引,单以name=‘张三’ 不会使用索引,考虑到存储空间的问题,还请根据业务需求,将查找频繁的数据进行靠左创建索引。索引下推:like 'hello%’and age >10 检索,MySQL5.6版本之前,会对匹配的数据进行回表查询。5.6版本后,会先过滤掉age<10的数据,再进行回表查询,减少回表率,提升检索速度。 参考资料极客时间MySQL实战45讲

June 1, 2019 · 1 min · jiezi

MariaDB-开启慢查询-Log找出到底慢在哪个-Query-语句上-Slow-query-logHigh-CPU

本教学使用环境介绍伺服器端:Ubuntu 18.04 LTS资料库:Mariadb 10.1.34(Mysql)语言版本:php 7.3本机端:MacOS High Sierra 网路上写的设定档是 /etc/mysql/my.cnf但我实际去找是在 mariadb.conf.d 底下的 50-server.cnf 才是(可能是 Mysql 跟 MariaDB 的差别?) $ cd /etc/mysql/mariadb.conf.d使用 nano 开启 $ nano 50-server.cnf进入后会看到 [mysqld] ,直接在下面加上 slow_query 变成: [mysqld]# 开启慢日志功能slow_query_log = 1# 查询时间超过 2 秒则定义为慢查询(可自行改秒数)long_query_time = 2# 将产生的 slow query log 放到你指定的地方slow_query_log_file = /var/www/slow_query.log保存后别忘了重启资料库 systemctl restart mariadb.service验证是否成功开启先透过 CLI 进入 Mysql (Mariadb) $ mysql -u root -p进入后输入指令以下 MariaDB [(none)]> 简称为 > > show variables like '%quer%';会出现以下表格 ...

May 16, 2019 · 1 min · jiezi

InnoDB引擎B树索引使用和新特性

我们已经讲过了MySQL InnoDB索引原理和算法,这里来说下如何管理和使用B+树索引以及一些新的特性。 B+ 树索引的管理我们在InnoDB引擎中常用的索引基本都是B+ 树索引。 创建和删除索引它的创建和删除有两种方法: # 方式一:alter table, 此时index和key均可以,如果要求所有值均不重复,加上uniquealter table tbl_name add [unique] index|key index_name (index_col_name,...);alter table tbl_name drop index|key index_name;# 方式二:create/drop index, 此时只能用indexcreate index index_name on tbl_name (index_col_name,...);drop index index_name on tbl_name;修改索引MySQL没有提供修改索引的命令,我们一般先删掉索引,再重建同名索引达到修改的目标。 查看索引我们在查看数据表描述时可以看到都有哪些索引,有三种方法: # 方法一:查看创建表的语句mysql> show create table serviceHost;| Table | Create Table | t | CREATE TABLE `t` ( `id` int(11) NOT NULL AUTO_INCREMENT, `a` varchar(20) DEFAULT NULL, `b` varchar(20) DEFAULT NULL, `c` varchar(20) DEFAULT NULL, `d` varchar(20) DEFAULT NULL, `e` varchar(20) DEFAULT NULL, `f` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `a` (`a`), KEY `idx_b` (`b`), KEY `idex_cde` (`c`,`d`,`e`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 |1 row in set (0.00 sec)可以看到该表中有4个索引,主键集合索引(id),唯一辅助索引(a),单列辅助索引(b),组合辅助索引(c,d,e)。 ...

May 14, 2019 · 8 min · jiezi

【数据库】MySQL查询优化

欢迎关注公众号:【爱编程】如果有需要后台回复2019赠送1T的学习资料哦!! 背景在这个快速发展的时代,时间变得越来越重要,也流逝得非常得快,有些人长大了,有些人却变老了。稍不留神,2019已经过完了三分之一。回首这四个月收获什么,懂得了什么?欢迎留言分享给我哟。 **言归正传:MySQL的查询怎么才能更快,更合理?除了加索引还有什么可以学习的呢?** 原理要想更好地学习某样东西,从其原理和运作方式入手更容易掌握。道理你们都懂,我就不废话了。 MySQL发送查询请求,到底做了什么工作?下图是MySQL查询执行流程图: 客户端发送一条查询给服务器。服务器先检查查询缓存,如果命中了缓存,则立刻返回查询在缓存中的结果。否则会进入下一个阶段。3.服务端进行SQL解析、预处理、再由优化器生成对应的执行计划。4.MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询。5.将结果返回给客户端。 是什么导致MySQL查询变慢了?对于MySQL,最简单的衡量查询开销的三个指标如下: 响应时间扫描的行数返回的行数没有哪个指标能够完美地衡量查询的开销,但它们大致反映了MySQL在内部执行查询时需要访问多少数据,并可以大概推算出查询运行的时间。 查询慢的原因基本都是:我们的不合理操作导致查询的多余数据太多了。常见原因有以下: 1.查询不需要的记录。2.多表关联时返回全部列3.总是取出全部列常用优化技巧1.用索引最简单且见效最快的方式就是给你的条件加索引(主键索引,普通索引,唯一索引等)。注:索引是要另开辟一块空间存储的,所以不能不要钱滴都加索引。 2.关联子查询MySQL的子查询实现是非常糟糕的。比如下面的 SELECT * FROM book WHERE book_id IN (SELECT book_id FROM author WHERE author_id = 1)MySQL对IN()列表中的选项有专门的优化策略,一般会认为MySQL会先执行子查询返回所有包含author_id 为1的book_id。 或许你想MySQL的运行时这样子的: SELECT GROUP_CONCAT(book_id) FROM author WHERE author_id = 1SELECT * FROM book WHERE book_id IN (1,21,3,45,656,766,213,123)但是,MySQL会将相关的外层表压到子查询中的,就是下面的样子: SELECT * FROM book WHERE EXISTS (SELECT * FROM author WHERE author_id = 1 AND book.book_id = author.book_id)原因:因为子查询需要book_id ,所以MySQL认为无法先执行这个子查询,而是先对book 进行全表扫描,然后再根据book_id进行子查询。具体可以EXPLAIN该SQL进行分析。 建议:1.使用左外连接(LEFT OUTER JOIN)代替子查询。 ...

April 21, 2019 · 2 min · jiezi

Mysql 优化点

具体参考: https://blog.csdn.net/luoyang…

April 10, 2019 · 1 min · jiezi

MySQL 执行过程与查询缓存

MySQL执行一个查询过程:当我们向MySQL发送一个请求的时候,MySQL到底做了什么:1.客户端发送一条查询给服务器2.服务器先检查查询缓存,如果命中了缓存,则立刻返回存储在缓存中的结果。否则进入下一阶段。3.服务器端进行SQL解析、预处理,再由优化器生成对应的执行计划。4.MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询5.将结果返回给客户端。mysql 主要是由 server 层和存储层两部分构成的。server 层主要包括连接器、查询缓存,分析器、优化器、执行器。存储层主要是用来存储和查询数据的,常用的存储引擎有 InnoDB、MyISAM,(1) MySQL客户端/服务器通信协议MySQL客户端和服务器之的通信协议是“半双工”的,这就意味着,在任何一个时刻,要么是由服务器向客户端发送数据,要么是由客户端向服务器发送数据,这两个动作不能同时发生。所以我们无法也无须将一个消息切成小块独立来发送。优缺点:这种协议让MySQL通信简单快速,但是也从很多地方限制了 MySQL。一个明显的限制是,这意味着没法进行流量控制。一旦一端开始发送消息,另一端要接收完整个消息才能响应它。这就像采回抛球的游戏:在任何时刻,只有一个人能控制球,而且只有控制球的人才能将球抛回去(发送消息)。(2).连接器 MySQL客户端和服务端建立连接,获取当前连接用户的权限(3)查询缓存在解析一个查询语句之前,如果查询缓存是打开的,MySQL会检查这个缓存,是否命中查询缓存中的数据。这个检查是通过一个大小写敏感的哈希查找实现的。查询和缓存中的查询即使只有一个字节不同,那也不会匹配缓存结果,这种情况下查询就会进入下一阶段的处理。如果当前的查询恰好命中了查询缓存,那么在返回查询结果之前 MySQL会检查一次用户权限。这仍然是无须解析查询SQL语句的,因为在查询缓存中已经存放了当前查询需要访问的表信息。如果权限没有问题, MySQL会跳过所有其他阶段,直接从缓存中拿到结果并返回给客户端。这种情况下,查询不会被解析,不用生成执行计划,不会被执行.ps:注意在 mysql8 后已经没有查询缓存这个功能了,因为这个缓存非常容易被清空掉,命中率比较低。(3).分析器既然没有查到缓存,就需要开始执行 sql 语句了,在执行之前肯定需要先对 sql 语句进行解析。分析器主要对 sql 语句进行语法和语义分析,检查单词是否拼写错误,还有检查要查询的表或字段是否存在(4)查询优化查询的生命周期的下一步是将一个SQL转换成一个执行计划, MySQL再依照这个执行计划和存储引擎进行交互。这包括多个子阶段:解析SQL、预处理、优化SQ执行计划。这个过程中任何错误(例如语法错误)都可能终止查询。2.关于查询缓存(1)MySQL 判断缓存命中的方法很简单:缓存存放在一个引用表中,通过一个哈希值引用。MySOL查询缓存保存查询返回的完整结果。当查询命中该缓存, MySQL会立刻返回结果跳过了 解析,优化和执行阶段查询缓存系统会跟踪查迫中涉及的每个表,如果这些表发生变化,那么和这个表相关的的存数据都将失效。这种机制效率看起来比较低,因为数据表变化时很有可能对查询结果并没有变更,但是这种简单实现代价很小,而这点对于一个非常繁忙的系统来说非常重要。查询缓存系统对应用程序是完全透明的。应用程序无须关心 MySQL是通过查询缓存返回的结果还是实际执行返回的结果。事实上,这两种方式执行的结果是完全相同的。换句话说,查询缓存无须使用任何语法。无论是 MYSQL开启成关闭查询缓在,对应用程序都是透明的。(2)判断缓存命中当判断缓存是否命中时, MySQL不会解析、“正规化”或者参数化查询语句,而是直接使用SQL语句和客户端发送过来的其他原始信息,在字符上不同,例如空格、注释,在何的不同,都会导致缓存的不中。当查询语句中有一些不确定的数据时,则不会被缓存,例如包含函数NOW()或者 CURRENT_DATE()的查询不会被缓存.误区:我们常听到:“如果查询中包含一个不确定的函数, MySQL则不会检查查询缓存”。这个说法是不正确的。因为在检查查询缓存的时候,还没有解析SQL语句,所以MySQL并不知道查询语句中是否包含这类函数。在检查查询缓存之前, MySQL只做一件事情,就是通过一个大小写不敏感的检查看看SQL语句是不是以5EL开头。准确的说法应该是:“如果查询语句中包含任何的不确定函数,那么在查询缓存中是不可能找到缓存结果的”。注意点:MySQL的查询缓存在很多时候可以提升查询性能,在使用的时候,有一些问题需要特别注意。首先,打开查询缓存对读和写操作都会带来额外的消耗:1.读查询在开始之前必须先检查是否命中缓存2.如果这个读查询可以被缓存,那么当完成执行后, MySQL若发现查询缓存中没有这个查询,会将其结果存入查询缓存,这会带来额外的系统消耗。3.这对写操作也会有影响,因为当向某个表写入数据的时候, MySQL必须将对应表的所有缓存都设置失效。如果查询缓存非常大或者碎片很多,这个操作就可能会带来大系统消耗(设置了很多的内存给查询缓存用的时候)如果查询缓存使用了很大量的内存,缓存失效操作就可能成为一个非常严重的问题瓶颈如果缓存中存放了大量的查询结果,那么缓存失效操作时整个系统都可能会僵死一会因为这个操作是靠一个全局锁操作保护的,所有需要做该操作的查询都要等待这个锁,而且无论是检测是否命中缓存、还是缓存失效检测都需要等待这个全局锁。(3)什么情况下查询缓存能发挥作用理论上,可以通过观察打开或者关闭查询缓存时候的系统效率来决定是否需要开启查询。对手那些需要消耗大量资源的查询通常都是非常适合缓存的。例如一些汇总计算查询具体的如 COUNT()等。总地来说,对于复杂的 SELECT语句都可以使用查询缓存,例如多表JOIN后还需要做排序和分页,这类查询每次执行消耗都很大,但是返回的结果集却很小,非常适合查询缓存。不过需要注意的是,涉及的表上 UPDATE、 DELETE和 INSERT操作相比 SELECT来说要非常少才行。判断查询缓存是否有效的直接数据是命中率。就是使用查询缓存返回结果占总查询的比率不过缓存中率是一个很难判断的数值。命中率多大才是好的命中率。具体情况,具体分析。只要查询缓存带来的效率提升大于查询缓存带来的额外消耗,即使30%命中率对系统性能提升也有很大好处。另外,缓存了哪些查询也很重要,例如,被缓存的查询本身消耗非常巨大,那么即使缓存命中率非常低,也仍然会对系统性能提升有好处缓存未命中可能有如下几种原因:1.查询语句无法被缓存,可能是因为查询中包含一个不确定的函数(如 CURREN_DATE)或者查询结果太大而无法缓存。这都会导致状态值 Cache not cached增加。2.MySQL从未处理这个查询,所以结果也从不曾被缓存过。3.还有一种情况是虽然之前缓存了查询结果,但是由于查询缓存的内存用完了,MySQL需要将某些缓存“逐出”,或者由于数据表被修改导致缓存失效。如果你的服务器上有大量缓存未命中,但是实际上绝大数查询都被缓存了,那么一定是有如下情况发生:1.查询缓存还没有完成预热。也就是说, MySQL还没有机会将查询结果都缓存起来。2.查询语句之前从未执行过。如果你的应用程序不会重复执行一条查询语句,那么即使完成预热仍然会有很多缓存未命中3.缓存失效操作太多了。(4)如何配置 和维护查询缓存query_cache_type是否打开查询缓存。可以设置成0FN或 DEMAND。 DEMAND表示只有在查询语句中明确写明SQL_ CACHE的语句才放入查询缓存。这个变量可以是会话级别的也可以是全局级别的query_cache_size查询缓存使用的总内存空间,单位是字节。这个值必须是1024的整数倍,否则 MySQL实际分配的数据会和你指定的略有不同。query_cahce_min_res_unit在查询缓存中分配内存块时的最小单位。query_chache_limitMySQL能够缓存的最大査询结果。如果查询结果大于这个值,则不会被缓存。因为査询缓存在数据生成的时候就开始尝试缓存数据,所以只有当结果全部返回后,才知道查询结果是否超出限制如果超出, MySQL则增加状态值 Cache_not_cached,并将结果从查询缓存中删除如果你事先知道有很多这样的情况发生,那么建议在查询语句中加入(5)替代方案MySQL查询缓存工作的原则是:执行查询最快的方式就是不去执行,但是查询仍然需要发送到服务器端,服务器也还需要做一点点工作。如果对于某些查询完全不需要与服务器通信效果会如何呢?这时客户端的缓存可以很大程度上帮你分担 MySQL服务器的压力总结:完全相同的查询在重复执行的时候,查询缓存可以立即返回结果,而无须在数据库中重新执行一次。根据我们的经验,在高并发压力环境中在询缓存会导致系统性能的下降,甚至僵死。如果一定要使用查询缓存,那么不要设置太大内存,而且只有在确收益的时候才使用。那该如何判断是否应该使用查询缓存呢?建议使Percona server.,观察更细致的日志,并做一些简单的计算。还可以查看缓存命中率(并不总是有用)、“ NSERTS和 SELECT比率”(这个参数也并不直观)、或者“命中和写入比率”(这个参考意义较大)。查询缓存是一个非常方便的缓存,对应用程序完全透明,无须任何额外的编码,但是、如果希望有更高的缓存效率,我们建议使cache 或者其他类似的解决方案。ps:文章参考《高性能MySQL》一书

March 30, 2019 · 1 min · jiezi

一次 group by + order by 性能优化分析

原文:我的个人博客 https://mengkang.net/1302.html工作了两三年,技术停滞不前,迷茫没有方向,不如看下我的直播 PHP 进阶之路 (金三银四跳槽必考,一般人我不告诉他)最近通过一个日志表做排行的时候发现特别卡,最后问题得到了解决,梳理一些索引和MySQL执行过程的经验,但是最后还是有5个谜题没解开,希望大家帮忙解答下主要包含如下知识点用数据说话证明慢日志的扫描行数到底是如何统计出来的从 group by 执行原理找出优化方案排序的实现细节gdb 源码调试背景需要分别统计本月、本周被访问的文章的 TOP10。日志表如下CREATE TABLE article_rank ( id int(11) unsigned NOT NULL AUTO_INCREMENT, aid int(11) unsigned NOT NULL, pv int(11) unsigned NOT NULL DEFAULT ‘1’, day int(11) NOT NULL COMMENT ‘日期 例如 20171016’, PRIMARY KEY (id), KEY idx_day_aid_pv (day,aid,pv), KEY idx_aid_day_pv (aid,day,pv)) ENGINE=InnoDB DEFAULT CHARSET=utf8准备工作为了能够清晰的验证自己的一些猜想,在虚拟机里安装了一个 debug 版的 mysql,然后开启了慢日志收集,用于统计扫描行数安装下载源码编译安装创建 mysql 用户初始化数据库初始化 mysql 配置文件修改密码如果你兴趣,具体可以参考我的博客,一步步安装 https://mengkang.net/1335.html开启慢日志编辑配置文件,在[mysqld]块下添加slow_query_log=1slow_query_log_file=xxxlong_query_time=0log_queries_not_using_indexes=1性能分析发现问题假如我需要查询2018-12-20 ~ 2018-12-24这5天浏览量最大的10篇文章的 sql 如下,首先使用explain看下分析结果mysql> explain select aid,sum(pv) as num from article_rank where day>=20181220 and day<=20181224 group by aid order by num desc limit 10;+—-+————-+————–+————+——-+——————————-+—————-+———+——+——–+———-+———————————————————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+————–+————+——-+——————————-+—————-+———+——+——–+———-+———————————————————–+| 1 | SIMPLE | article_rank | NULL | range | idx_day_aid_pv,idx_aid_day_pv | idx_day_aid_pv | 4 | NULL | 404607 | 100.00 | Using where; Using index; Using temporary; Using filesort |+—-+————-+————–+————+——-+——————————-+—————-+———+——+——–+———-+———————————————————–+系统默认会走的索引是idx_day_aid_pv,根据Extra信息我们可以看到,使用idx_day_aid_pv索引的时候,会走覆盖索引,但是会使用临时表,会有排序。我们查看下慢日志里的记录信息# Time: 2019-03-17T03:02:27.984091Z# User@Host: root[root] @ localhost [] Id: 6# Query_time: 56.959484 Lock_time: 0.000195 Rows_sent: 10 Rows_examined: 1337315SET timestamp=1552791747;select aid,sum(pv) as num from article_rank where day>=20181220 and day<=20181224 group by aid order by num desc limit 10;为什么扫描行数是 1337315我们查询两个数据,一个是满足条件的行数,一个是group by统计之后的行数。mysql> select count() from article_rank where day>=20181220 and day<=20181224;+———-+| count() |+———-+| 785102 |+———-+mysql> select count(distinct aid) from article_rank where day>=20181220 and day<=20181224;+———————+| count(distinct aid) |+———————+| 552203 |+———————+发现满足条件的总行数(785102)+group by 之后的总行数(552203)+limit 的值 = 慢日志里统计的 Rows_examined。要解答这个问题,就必须搞清楚上面这个 sql 到底分别都是如何运行的。执行流程分析索引示例为了便于理解,我按照索引的规则先模拟idx_day_aid_pv索引的一小部分数据dayaidpvid20181220123123420181220321231201812204112122018122072122120181221151257201812211011251201812211181258因为索引idx_day_aid_pv最左列是day,所以当我们需要查找2018122020181224之间的文章的pv总和的时候,我们需要遍历2018122020181224这段数据的索引。查看 optimizer trace 信息# 开启 optimizer_traceset optimizer_trace=‘enabled=on’;# 执行 sql select aid,sum(pv) as num from article_rank where day>=20181220 and day<=20181224 group by aid order by num desc limit 10;# 查看 trace 信息select trace from information_schema.optimizer_trace\G;摘取里面最后的执行结果如下{ “join_execution”: { “select#”: 1, “steps”: [ { “creating_tmp_table”: { “tmp_table_info”: { “table”: “intermediate_tmp_table”, “row_length”: 20, “key_length”: 4, “unique_constraint”: false, “location”: “memory (heap)”, “row_limit_estimate”: 838860 } } }, { “converting_tmp_table_to_ondisk”: { “cause”: “memory_table_size_exceeded”, “tmp_table_info”: { “table”: “intermediate_tmp_table”, “row_length”: 20, “key_length”: 4, “unique_constraint”: false, “location”: “disk (InnoDB)”, “record_format”: “fixed” } } }, { “filesort_information”: [ { “direction”: “desc”, “table”: “intermediate_tmp_table”, “field”: “num” } ], “filesort_priority_queue_optimization”: { “limit”: 10, “rows_estimate”: 1057, “row_size”: 36, “memory_available”: 262144, “chosen”: true }, “filesort_execution”: [ ], “filesort_summary”: { “rows”: 11, “examined_rows”: 552203, “number_of_tmp_files”: 0, “sort_buffer_size”: 488, “sort_mode”: “<sort_key, additional_fields>” } } ] }}分析临时表字段mysql gdb 调试更多细节 https://mengkang.net/1336.html通过gdb调试确认临时表上的字段是aid和numBreakpoint 1, trace_tmp_table (trace=0x7eff94003088, table=0x7eff94937200) at /root/newdb/mysql-server/sql/sql_tmp_table.cc:2306warning: Source file is more recent than executable.2306 trace_tmp.add(“row_length”,table->s->reclength).(gdb) p table->s->reclength$1 = 20(gdb) p table->s->fields$2 = 2(gdb) p ((table->field+0))->field_name$3 = 0x7eff94010b0c “aid”(gdb) p ((table->field+1))->field_name$4 = 0x7eff94007518 “num”(gdb) p ((table->field+0))->row_pack_length()$5 = 4(gdb) p ((table->field+1))->row_pack_length()$6 = 15(gdb) p ((table->field+0))->type()$7 = MYSQL_TYPE_LONG(gdb) p ((table->field+1))->type()$8 = MYSQL_TYPE_NEWDECIMAL(gdb)通过上面的打印,确认了字段类型,一个aid是MYSQL_TYPE_LONG,占4字节,num是MYSQL_TYPE_NEWDECIMAL,占15字节。The SUM() and AVG() functions return a DECIMAL value for exact-value arguments (integer or DECIMAL), and a DOUBLE value for approximate-value arguments (FLOAT or DOUBLE). (Before MySQL 5.0.3, SUM() and AVG() return DOUBLE for all numeric arguments.)但是通过我们上面打印信息可以看到两个字段的长度加起来是19,而optimizer_trace里的tmp_table_info.reclength是20。通过其他实验也发现table->s->reclength的长度就是table->field数组里面所有字段的字段长度和再加1。总结执行流程尝试在堆上使用memory的内存临时表来存放group by的数据,发现内存不够;创建一张临时表,临时表上有两个字段,aid和num字段(sum(pv) as num);从索引idx_day_aid_pv中取出1行,插入临时表。插入规则是如果aid不存在则直接插入,如果存在,则把pv的值累加在num上;循环遍历索引idx_day_aid_pv上2018122020181224之间的所有行,执行步骤3;对临时表根据num的值做优先队列排序;取出最后留在堆(优先队列的堆)里面的10行数据,作为结果集直接返回,不需要再回表;补充说明优先队列排序执行步骤分析:在临时表(未排序)中取出前 10 行,把其中的num和aid作为10个元素构成一个小顶堆,也就是最小的 num 在堆顶。取下一行,根据 num 的值和堆顶值作比较,如果该字大于堆顶的值,则替换掉。然后将新的堆做堆排序。重复步骤2直到第 552203 行比较完成。优化方案1 使用 idx_aid_day_pv 索引# Query_time: 4.406927 Lock_time: 0.000200 Rows_sent: 10 Rows_examined: 1337315SET timestamp=1552791804;select aid,sum(pv) as num from article_rank force index(idx_aid_day_pv) where day>=20181220 and day<=20181224 group by aid order by num desc limit 10;扫描行数都是1337315,为什么执行消耗的时间上快了12倍呢?索引示例为了便于理解,同样我也按照索引的规则先模拟idx_aid_day_pv索引的一小部分数据aiddaypvid12018122023123412018122151257320181220212313201812222213313201812241314314201812201121272018122021221102018122111251112018122181258group by 不需要临时表的情况为什么性能上比 SQL1 高了,很多呢,原因之一是idx_aid_day_pv索引上aid是确定有序的,那么执行group by的时候,则不会创建临时表,排序的时候才需要临时表。如果印证这一点呢,我们通过下面的执行计划就能看到使用idx_day_aid_pv索引的效果:mysql> explain select aid,sum(pv) as num from article_rank force index(idx_day_aid_pv) where day>=20181220 and day<=20181224 group by aid order by null limit 10;+—-+————-+————–+————+——-+——————————-+—————-+———+——+——–+———-+——————————————-+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+————–+————+——-+——————————-+—————-+———+——+——–+———-+——————————————-+| 1 | SIMPLE | article_rank | NULL | range | idx_day_aid_pv,idx_aid_day_pv | idx_day_aid_pv | 4 | NULL | 404607 | 100.00 | Using where; Using index; Using temporary |+—-+————-+————–+————+——-+——————————-+—————-+———+——+——–+———-+——————————————-+注意我上面使用了order by null表示强制对group by的结果不做排序。如果不加order by null,上面的 sql 则会出现Using filesort使用idx_aid_day_pv索引的效果:mysql> explain select aid,sum(pv) as num from article_rank force index(idx_aid_day_pv) where day>=20181220 and day<=20181224 group by aid order by null limit 10;+—-+————-+————–+————+——-+——————————-+—————-+———+——+——+———-+————————–+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |+—-+————-+————–+————+——-+——————————-+—————-+———+——+——+———-+————————–+| 1 | SIMPLE | article_rank | NULL | index | idx_day_aid_pv,idx_aid_day_pv | idx_aid_day_pv | 12 | NULL | 10 | 11.11 | Using where; Using index |+—-+————-+————–+————+——-+——————————-+—————-+———+——+——+———-+————————–+查看 optimizer trace 信息# 开启optimizer_traceset optimizer_trace=‘enabled=on’;# 执行 sql select aid,sum(pv) as num from article_rank force index(idx_aid_day_pv) where day>=20181220 and day<=20181224 group by aid order by num desc limit 10;# 查看 trace 信息select trace from information_schema.optimizer_trace\G;摘取里面最后的执行结果如下{ “join_execution”: { “select#”: 1, “steps”: [ { “creating_tmp_table”: { “tmp_table_info”: { “table”: “intermediate_tmp_table”, “row_length”: 20, “key_length”: 0, “unique_constraint”: false, “location”: “memory (heap)”, “row_limit_estimate”: 838860 } } }, { “filesort_information”: [ { “direction”: “desc”, “table”: “intermediate_tmp_table”, “field”: “num” } ], “filesort_priority_queue_optimization”: { “limit”: 10, “rows_estimate”: 552213, “row_size”: 24, “memory_available”: 262144, “chosen”: true }, “filesort_execution”: [ ], “filesort_summary”: { “rows”: 11, “examined_rows”: 552203, “number_of_tmp_files”: 0, “sort_buffer_size”: 352, “sort_mode”: “<sort_key, rowid>” } } ] }}执行流程如下创建一张临时表,临时表上有两个字段,aid和num字段(sum(pv) as num);读取索引idx_aid_day_pv中的一行,然后查看是否满足条件,如果day字段不在条件范围内(2018122020181224之间),则读取下一行;如果day字段在条件范围内,则把pv值累加(不是在临时表中操作);读取索引idx_aid_day_pv中的下一行,如果aid与步骤1中一致且满足条件,则pv值累加(不是在临时表中操作)。如果aid与步骤1中不一致,则把之前的结果集写入临时表;循环执行步骤2、3,直到扫描完整个idx_aid_day_pv索引;对临时表根据num的值做优先队列排序;根据查询到的前10条的rowid回表(临时表)返回结果集。补充说明优先队列排序执行步骤分析:在临时表(未排序)中取出前 10 行,把其中的num和rowid作为10个元素构成一个小顶堆,也就是最小的 num 在堆顶。取下一行,根据 num 的值和堆顶值作比较,如果该字大于堆顶的值,则替换掉。然后将新的堆做堆排序。重复步骤2直到第 552203 行比较完成。该方案可行性实验发现,当我增加一行20181219的数据时,虽然这行记录不满足我们的需求,但是扫描索引的也会读取这行。因为我做这个实验,只弄了20181220~201812245天的数据,所以需要扫描的行数正好是全表数据行数。那么如果该表的数据存储的不是5天的数据,而是10天的数据呢,更或者是365天的数据呢?这个方案是否还可行呢?先模拟10天的数据,在现有时间基础上往后加5天,行数与现在一样785102行。drop procedure if exists idata;delimiter ;;create procedure idata()begin declare i int; declare aid int; declare pv int; declare post_day int; set i=1; while(i<=785102)do set aid = round(rand()*500000); set pv = round(rand()*100); set post_day = 20181225 + i%5; insert into article_rank (aid,pv,day) values(aid, pv, post_day); set i=i+1; end while;end;;delimiter ;call idata();# Query_time: 9.151270 Lock_time: 0.000508 Rows_sent: 10 Rows_examined: 2122417SET timestamp=1552889936;select aid,sum(pv) as num from article_rank force index(idx_aid_day_pv) where day>=20181220 and day<=20181224 group by aid order by num desc limit 10;这里扫描行数2122417是因为扫描索引的时候需要遍历整个索引,整个索引的行数就是全表行数,因为我刚刚又插入了785102行。当我数据量翻倍之后,这里查询时间明显已经翻倍。所以这个优化方式不稳定。方案2 扩充临时表空间上限大小默认的临时表空间大小是16MBmysql> show global variables like ‘%table_size’;+———————+———-+| Variable_name | Value |+———————+———-+| max_heap_table_size | 16777216 || tmp_table_size | 16777216 |+———————+———-+https://dev.mysql.com/doc/ref…https://dev.mysql.com/doc/ref...max_heap_table_sizeThis variable sets the maximum size to which user-created MEMORY tables are permitted to grow. The value of the variable is used to calculate MEMORY table MAX_ROWS values. Setting this variable has no effect on any existing MEMORY table, unless the table is re-created with a statement such as CREATE TABLE or altered with ALTER TABLE or TRUNCATE TABLE. A server restart also sets the maximum size of existing MEMORY tables to the global max_heap_table_size value.tmp_table_size The maximum size of internal in-memory temporary tables. This variable does not apply to user-created MEMORY tables.The actual limit is determined from whichever of the values of tmp_table_size and max_heap_table_size is smaller. If an in-memory temporary table exceeds the limit, MySQL automatically converts it to an on-disk temporary table. The internal_tmp_disk_storage_engine option defines the storage engine used for on-disk temporary tables.也就是说这里临时表的限制是16M,max_heap_table_size大小也受tmp_table_size大小的限制。所以我们这里调整为32MB,然后执行原始的SQLset tmp_table_size=33554432;set max_heap_table_size=33554432;# Query_time: 5.910553 Lock_time: 0.000210 Rows_sent: 10 Rows_examined: 1337315SET timestamp=1552803869;select aid,sum(pv) as num from article_rank where day>=20181220 and day<=20181224 group by aid order by num desc limit 10;方案3 使用 SQL_BIG_RESULT 优化告诉优化器,查询结果比较多,临时表直接走磁盘存储。# Query_time: 6.144315 Lock_time: 0.000183 Rows_sent: 10 Rows_examined: 2122417SET timestamp=1552802804;select SQL_BIG_RESULT aid,sum(pv) as num from article_rank where day>=20181220 and day<=20181224 group by aid order by num desc limit 10;扫描行数是 2x满足条件的总行数(785102)+group by 之后的总行数(552203)+limit 的值。顺便值得一提的是: 当我把数据量翻倍之后,使用该方式,查询时间基本没变。因为扫描的行数还是不变的。实际测试耗时6.197484总结方案1优化效果不稳定,当总表数据量与查询范围的总数相同时,且不超出内存临时表大小限制时,性能达到最佳。当查询数据量占据总表数据量越大,优化效果越不明显;方案2需要调整临时表内存的大小,可行;不过当数据库超过32MB时,如果使用该方式,还需要继续提升临时表大小;方案3直接声明使用磁盘来放临时表,虽然扫描行数多了一次符合条件的总行数的扫描。但是整体响应时间比方案2就慢了0.1秒。因为我们这里数据量比较,我觉得这个时间差还能接受。所以最后对比,选择方案3比较合适。问题与困惑# SQL1select aid,sum(pv) as num from article_rank where day>=20181220 and day<=20181224 group by aid order by num desc limit 10;# SQL2select aid,sum(pv) as num from article_rank force index(idx_aid_day_pv) where day>=20181220 and day<=20181224 group by aid order by num desc limit 10;SQL1 执行过程中,使用的是全字段排序最后不需要回表为什么总扫描行数还要加上10才对得上?SQL1 与 SQL2 group by之后得到的行数都是552203,为什么会出现 SQL1 内存不够,里面还有哪些细节呢?trace 信息里的creating_tmp_table.tmp_table_info.row_limit_estimate都是838860;计算由来是临时表的内存限制大小16MB,而一行需要占的空间是20字节,那么最多只能容纳floor(16777216/20) = 838860行,而实际我们需要放入临时表的行数是785102。为什么呢?SQL1 使用SQL_BIG_RESULT优化之后,原始表需要扫描的行数会乘以2,背后逻辑是什么呢?为什么仅仅是不再尝试往内存临时表里写入这一步会相差10多倍的性能?通过源码看到 trace 信息里面很多扫描行数都不是实际的行数,既然是实际执行,为什么 trace 信息里不输出真实的扫描行数和容量等呢,比如filesort_priority_queue_optimization.rows_estimate在SQL1中的扫描行数我通过gdb看到计算规则如附录图 1有没有工具能够统计 SQL 执行过程中的 I/O 次数?附录 ...

March 18, 2019 · 6 min · jiezi

MySQL 千万级数据表 partition 实战应用

目前系统的 Stat 表以每天 20W 条的数据量增加,尽管已经把超过3个月的数据 dump 到其他地方,但表中仍然有接近 2KW 条数据,容量接近 2GB。Stat 表已经加上索引,直接 select … where … limit 的话,速度还是很快的,但一旦涉及到 group by 分页,就会变得很慢。据观察,7天内的 group by 需要 35~50s 左右。运营反映体验极其不友好。于是上网搜索 MySQL 分区方案。发现网上的基本上都是在系统性地讲解 partition 的概念和种类,以及一些实验性质的效果,并不贴近实战。通过参考 MySQL手册以及自己的摸索,最终在当前系统中实现了分区,因为记录一下。分区类型的选择Stat 表本身是一个统计报表,所以它的数据都是按日期来存放的,并且热数据一般只限于当天,以及7天内。所以我选择了 Range 类型来进行分区。为当前表创建分区因为是对已有表进行改造,所以只能用 alter 的方式:ALTER TABLE stat PARTITION BY RANGE(TO_DAYS(dt)) ( PARTITION p0 VALUES LESS THAN(0), PARTITION p190214 VALUES LESS THAN(TO_DAYS(‘2019-02-14’)), PARTITION pm VALUES LESS THAN(MAXVALUE) );这里有2点要注意:一是 p0 分区,这是因为 MySQL(我是5.7版) 有个 bug,就是不管你查的数据在哪个区,它都会扫一下第一个区,我们每个区的数据都有几十万条,扫一下很是肉疼啊,所以为了避免不必要的扫描,直接弄个0数据分区就行了。二是 pm 分区,这个是最大分区。假如不要 pm,那你存 2019-02-15 的数据就会报错。所以 pm 实际上是给未来的数据一个预留的分区。定期扩展分区由于 MySQL 的分区并不能自己动态扩容,所以我们要写个代码为它动态的增加分区。增加分区需要用到 REORGANIZE 命令,它的作用是对某个分区重新分配。比如明天是 15 号,那我们要给 15 号也增加个分区,实际上就是把 pm 分区拆分成2个分区:ALTER TABLE stat REORGANIZE PARTITION pm INTO ( PARTITION p190215 VALUES LESS THAN(TO_DAYS(‘2019-02-15’)), PARTITION pm VALUES LESS THAN(MAXVALUE) );这里就涉及到一个问题,即如何获得当前表的所有分区?网上有挺多方法,但我试了下感觉还是先 show create table stat 然后用正则匹配出所有分区更方便一点。定期删除分区随着数据库越来越大,我们肯定是要清除旧的数据,同时也要清除旧的分区。这个也比较简单:ALTER TABLE stat DROP PARTITION p190214, p190215 ...

February 21, 2019 · 1 min · jiezi

Mysql 使用 optimizer_trace 查看执行流程,分析、验证优化思路

该博客是我在看了《MySQL实战45讲》之后的一次实践笔记。文章比较枯燥,如果你在这篇文章看到一些陌生的关键字,建议你也一定要去做实验,只有做实验且验证了各个数据的由来,才能真正弄懂。背景Mysql 版本 :5.7业务需求:需要统最近一个月阅读量最大的10篇文章为了对比后面实验效果,我加了3个索引CREATE TABLE article_rank ( id int(11) unsigned NOT NULL AUTO_INCREMENT, aid int(11) unsigned NOT NULL, pv int(11) unsigned NOT NULL DEFAULT ‘1’, day int(11) NOT NULL COMMENT ‘日期 例如 20171016’, PRIMARY KEY (id), KEY idx_day (day), KEY idx_day_aid_pv (day,aid,pv), KEY idx_aid_day_pv (aid,day,pv)) ENGINE=InnoDB DEFAULT CHARSET=utf8实验原理Optimizer Trace 是MySQL 5.6.3里新加的一个特性,可以把MySQL Optimizer的决策和执行过程输出成文本,结果为JSON格式,兼顾了程序分析和阅读的便利。利用performance_schema库里面的session_status来统计innodb读取行数利用performance_schema库里面的optimizer_trace来查看语句执行的详细信息下面的实验都使用如下步骤来执行#0. 如果前面有开启 optimizer_trace 则先关闭SET optimizer_trace=“enabled=off”;#1. 开启 optimizer_traceSET optimizer_trace=‘enabled=on’;#2. 记录现在执行目标 sql 之前已经读取的行数select VARIABLE_VALUE into @a from performance_schema.session_status where variable_name = ‘Innodb_rows_read’;#3. 执行我们需要执行的 sqltodo#4. 查询 optimizer_trace 详情select trace from information_schema.optimizer_trace\G;#5. 记录现在执行目标 sql 之后读取的行数select VARIABLE_VALUE into @b from performance_schema.session_status where variable_name = ‘Innodb_rows_read’;官方文档 https://dev.mysql.com/doc/int…实验我做了四次实验,具体执行的第三步的 sql 如下实验sql实验1select aid,sum(pv) as num from article_rank force index(idx_day_aid_pv) where day>20190115 group by aid order by num desc LIMIT 10;实验2select aid,sum(pv) as num from article_rank force index(idx_day) where day>20190115 group by aid order by num desc LIMIT 10;实验3select aid,sum(pv) as num from article_rank force index(idx_aid_day_pv) where day>20190115 group by aid order by num desc LIMIT 10;实验4select aid,sum(pv) as num from article_rank force index(PRI) where day>20190115 group by aid order by num desc LIMIT 10;实验1mysql> select aid,sum(pv) as num from article_rank force index(idx_day_aid_pv) where day>‘20190115’ group by aid order by num desc LIMIT 10;# 结果省略10 rows in set (25.05 sec){ “steps”: [ { “join_preparation”: “略” }, { “join_optimization”: “略” }, { “join_execution”: { “select#”: 1, “steps”: [ { “creating_tmp_table”: { “tmp_table_info”: { “table”: “intermediate_tmp_table”, “row_length”: 20, “key_length”: 4, “unique_constraint”: false, “location”: “memory (heap)”, “row_limit_estimate”: 838860 } } }, { “converting_tmp_table_to_ondisk”: { “cause”: “memory_table_size_exceeded”, “tmp_table_info”: { “table”: “intermediate_tmp_table”, “row_length”: 20, “key_length”: 4, “unique_constraint”: false, “location”: “disk (InnoDB)”, “record_format”: “fixed” } } }, { “filesort_information”: [ { “direction”: “desc”, “table”: “intermediate_tmp_table”, “field”: “num” } ], “filesort_priority_queue_optimization”: { “limit”: 10, “rows_estimate”: 1057, “row_size”: 36, “memory_available”: 262144, “chosen”: true }, “filesort_execution”: [ ], “filesort_summary”: { “rows”: 11, “examined_rows”: 649091, “number_of_tmp_files”: 0, “sort_buffer_size”: 488, “sort_mode”: “<sort_key, additional_fields>” } } ] } } ]}mysql> select VARIABLE_VALUE into @b from performance_schema.session_status where variable_name = ‘Innodb_rows_read’;Query OK, 1 row affected (0.00 sec)mysql> select @b-@a;+———+| @b-@a |+———+| 6417027 |+———+1 row in set (0.01 sec)实验2mysql> select aid,sum(pv) as num from article_rank force index(idx_day) where day>‘20190115’ group by aid order by num desc LIMIT 10;# 结果省略10 rows in set (42.06 sec){ “steps”: [ { “join_preparation”: “略” }, { “join_optimization”: “略” }, { “join_execution”: { “select#”: 1, “steps”: [ { “creating_tmp_table”: { “tmp_table_info”: { “table”: “intermediate_tmp_table”, “row_length”: 20, “key_length”: 4, “unique_constraint”: false, “location”: “memory (heap)”, “row_limit_estimate”: 838860 } } }, { “converting_tmp_table_to_ondisk”: { “cause”: “memory_table_size_exceeded”, “tmp_table_info”: { “table”: “intermediate_tmp_table”, “row_length”: 20, “key_length”: 4, “unique_constraint”: false, “location”: “disk (InnoDB)”, “record_format”: “fixed” } } }, { “filesort_information”: [ { “direction”: “desc”, “table”: “intermediate_tmp_table”, “field”: “num” } ], “filesort_priority_queue_optimization”: { “limit”: 10, “rows_estimate”: 1057, “row_size”: 36, “memory_available”: 262144, “chosen”: true }, “filesort_execution”: [ ], “filesort_summary”: { “rows”: 11, “examined_rows”: 649091, “number_of_tmp_files”: 0, “sort_buffer_size”: 488, “sort_mode”: “<sort_key, additional_fields>” } } ] } } ]}mysql> select @b-@a;+———+| @b-@a |+———+| 9625540 |+———+1 row in set (0.00 sec)实验3mysql> select aid,sum(pv) as num from article_rank force index(idx_aid_day_pv) where day>‘20190115’ group by aid order by num desc LIMIT 10;# 省略结果10 rows in set (5.38 sec){ “steps”: [ { “join_preparation”: “略” }, { “join_optimization”: “略” }, { “join_execution”: { “select#”: 1, “steps”: [ { “creating_tmp_table”: { “tmp_table_info”: { “table”: “intermediate_tmp_table”, “row_length”: 20, “key_length”: 0, “unique_constraint”: false, “location”: “memory (heap)”, “row_limit_estimate”: 838860 } } }, { “filesort_information”: [ { “direction”: “desc”, “table”: “intermediate_tmp_table”, “field”: “num” } ], “filesort_priority_queue_optimization”: { “limit”: 10, “rows_estimate”: 649101, “row_size”: 24, “memory_available”: 262144, “chosen”: true }, “filesort_execution”: [ ], “filesort_summary”: { “rows”: 11, “examined_rows”: 649091, “number_of_tmp_files”: 0, “sort_buffer_size”: 352, “sort_mode”: “<sort_key, rowid>” } } ] } } ]}mysql> select VARIABLE_VALUE into @b from performance_schema.session_status where variable_name = ‘Innodb_rows_read’;Query OK, 1 row affected (0.00 sec)mysql> select @b-@a;+———-+| @b-@a |+———-+| 14146056 |+———-+1 row in set (0.00 sec)实验4mysql> select aid,sum(pv) as num from article_rank force index(PRI) where day>‘20190115’ group by aid order by num desc LIMIT 10;# 省略查询结果10 rows in set (21.90 sec){ “steps”: [ { “join_preparation”: “略” }, { “join_optimization”: “略” }, { “join_execution”: { “select#”: 1, “steps”: [ { “creating_tmp_table”: { “tmp_table_info”: { “table”: “intermediate_tmp_table”, “row_length”: 20, “key_length”: 4, “unique_constraint”: false, “location”: “memory (heap)”, “row_limit_estimate”: 838860 } } }, { “converting_tmp_table_to_ondisk”: { “cause”: “memory_table_size_exceeded”, “tmp_table_info”: { “table”: “intermediate_tmp_table”, “row_length”: 20, “key_length”: 4, “unique_constraint”: false, “location”: “disk (InnoDB)”, “record_format”: “fixed” } } }, { “filesort_information”: [ { “direction”: “desc”, “table”: “intermediate_tmp_table”, “field”: “num” } ], “filesort_priority_queue_optimization”: { “limit”: 10, “rows_estimate”: 1057, “row_size”: 36, “memory_available”: 262144, “chosen”: true }, “filesort_execution”: [ ], “filesort_summary”: { “rows”: 11, “examined_rows”: 649091, “number_of_tmp_files”: 0, “sort_buffer_size”: 488, “sort_mode”: “<sort_key, additional_fields>” } } ] } } ]}mysql> select VARIABLE_VALUE into @b from performance_schema.session_status where variable_name = ‘Innodb_rows_read’;Query OK, 1 row affected (0.00 sec)mysql> select @b-@a;+———-+| @b-@a |+———-+| 17354569 |+———-+1 row in set (0.00 sec)执行流程举例说明看下本案例中的 sql 去掉强制索引之后的语句select aid,sum(pv) as num from article_rank where day>20190115 group by aid order by num desc LIMIT 10;我们以实验1为例第一步因为该 sql 中使用了 group by,所以我们看到optimizer_trace在执行时(join_execution)都会先创建一张临时表creating_tmp_table)来存放group by子句之后的结果。存放的字段是aid和num两个字段。该临时表是如何存储的? row_length 为什么是 20? 另开三篇博客写了这个问题 https://mengkang.net/1334.htmlhttps://mengkang.net/1335.htmlhttps://mengkang.net/1336.html第二步因为memory_table_size_exceeded的原因,需要把临时表intermediate_tmp_table以InnoDB引擎存在磁盘。mysql> show global variables like ‘%table_size’;+———————+———-+| Variable_name | Value |+———————+———-+| max_heap_table_size | 16777216 || tmp_table_size | 16777216 |+———————+———-+https://dev.mysql.com/doc/ref…https://dev.mysql.com/doc/ref...max_heap_table_sizeThis variable sets the maximum size to which user-created MEMORY tables are permitted to grow. The value of the variable is used to calculate MEMORY table MAX_ROWS values. Setting this variable has no effect on any existing MEMORY table, unless the table is re-created with a statement such as CREATE TABLE or altered with ALTER TABLE or TRUNCATE TABLE. A server restart also sets the maximum size of existing MEMORY tables to the global max_heap_table_size value.tmp_table_size The maximum size of internal in-memory temporary tables. This variable does not apply to user-created MEMORY tables.The actual limit is determined from whichever of the values of tmp_table_size and max_heap_table_size is smaller. If an in-memory temporary table exceeds the limit, MySQL automatically converts it to an on-disk temporary table. The internal_tmp_disk_storage_engine option defines the storage engine used for on-disk temporary tables.也就是说这里临时表的限制是16M,而一行需要占的空间是20字节,那么最多只能容纳floor(16777216/20) = 838860行,所以row_limit_estimate是838860。我们统计下group by之后的总行数。mysql> select count(distinct aid) from article_rank where day>‘20190115’;+———————+| count(distinct aid) |+———————+| 649091 |+———————+649091 < 838860问题:为什么会触发memory_table_size_exceeded呢?数据写入临时表的过程如下:在磁盘上创建临时表,表里有两个字段,aid和num,因为是 group by aid,所以aid是临时表的主键。实验1中是扫描索引idx_day_aid_pv,依次取出叶子节点的aid和pv的值。如果临时表种没有对应的 aid就插入,如果已经存在的 aid,则把需要插入行的 pv 累加在原来的行上。第三步对intermediate_tmp_table里面的num字段做desc排序filesort_summary.examined_rows排序扫描行数统计,我们统计下group by之后的总行数。(前面算过是649091)所以每个实验的结果中filesort_summary.examined_rows 的值都是649091。filesort_summary.number_of_tmp_files的值为0,表示没有使用临时文件来排序。filesort_summary.sort_modeMySQL 会给每个线程分配一块内存用于排序,称为sort_buffer。sort_buffer的大小由sort_buffer_size来确定。mysql> show global variables like ‘sort_buffer_size’;+——————+——–+| Variable_name | Value |+——————+——–+| sort_buffer_size | 262144 |+——————+——–+1 row in set (0.01 sec)也就说是sort_buffer_size默认值是256KBhttps://dev.mysql.com/doc/ref…Default Value (Other, 64-bit platforms, >= 5.6.4) 262144排序的方式也是有多种的<sort_key, rowid><sort_key, additional_fields><sort_key, packed_additional_fields>additional_fields初始化sort_buffer,确定放入字段,因为我们这里是根据num来排序,所以sort_key就是num,additional_fields就是aid;把group by 子句之后生成的临时表(intermediate_tmp_table)里的数据(aid,num)存入sort_buffer。我们通过number_of_tmp_files值为0,知道内存是足够用的,并没有使用外部文件进行归并排序;对sort_buffer中的数据按num做快速排序;按照排序结果取前10行返回给客户端;rowid根据索引或者全表扫描,按照过滤条件获得需要查询的排序字段值和row ID;将要排序字段值和row ID组成键值对,存入sort buffer中;如果sort buffer内存大于这些键值对的内存,就不需要创建临时文件了。否则,每次sort buffer填满以后,需要在内存中排好序(快排),并写到临时文件中;重复上述步骤,直到所有的行数据都正常读取了完成;用到了临时文件的,需要利用磁盘外部排序,将row id写入到结果文件中;根据结果文件中的row ID按序读取用户需要返回的数据。由于row ID不是顺序的,导致回表时是随机IO,为了进一步优化性能(变成顺序IO),MySQL会读一批row ID,并将读到的数据按排序字段顺序插入缓存区中(内存大小read_rnd_buffer_size)。实验结果分析在看了附录中的实验结果之后,我汇总了一些比较重要的数据对比信息指标indexquery_timefilesort_summary.examined_rowsfilesort_summary.sort_modefilesort_priority_queue_optimization.rows_estimateconverting_tmp_table_to_ondiskInnodb_rows_read实验1idx_day_aid_pv25.05649091additional_fields1057true6417027实验2idx_day42.06649091additional_fields1057true9625540实验3idx_aid_day_pv5.38649091rowid649101false14146056实验4PRI21.90649091additional_fields1057true17354569filesort_summary.examined_rows实验1案例中已经分析过。mysql> select count(distinct aid) from article_rank where day>‘20190115’;+———————+| count(distinct aid) |+———————+| 649091 |+———————+filesort_summary.sort_mode同样的字段,同样的行数,为什么有的是additional_fields排序,有的是rowid排序呢?我们说 additional_fields 对比 rowid 来说,减少了回表,也就减少了磁盘访问,会被优先选择。但是要注意这是对于 InnoDB 来说的。而实验3是内存表,使用的是 memory 引擎。回表过程只是根据数据行的位置,直接访问内存得到数据,不会有磁盘访问(可以简单的理解为一个内存中的数组下标去找对应的元素)。排序的列越少越好占的内存就越小,所以就选择了 rowid 排序。关于内存表的排序详解,可以参考 MySQL实战45讲的第17讲如何正确地显示随机消息filesort_priority_queue_optimization.rows_estimate根据优先队列排序算法所理解:1.取出 649091 行(未排序)的前 10 行,构成一个堆。2.取下一行,根据 num (来源于sum(pv))的值和堆里面最小的值作比较,如果该字大于堆里面的值,则替换掉(原来堆的最小值被删掉)3.该节点与其父节点的值继续作比较,如果大于父节点的值则二者替换。递归执行,直到根节点4.重复步骤2,3直到第 649091 行比较完成根据这个分析,四个实验都应该是扫描 649091 行,但实际结果却是,实验3是 649091 + 10 行,其他的都是 1057 行。converting_tmp_table_to_ondisk是否创建临时表。同样是写入 649091 到内存临时表,为什么其他三种方式都会出现内存不够用的情况呢?Innodb_rows_read上面实验中每次在统计@b-@a的过程中,我们查询了OPTIMIZER_TRACE这张表,需要用到临时表,而 internal_tmp_disk_storage_engine 的默认值是 InnoDB。如果使用的是 InnoDB 引擎的话,把数据从临时表取出来的时候,会让 Innodb_rows_read 的值加 1。我们先查询下面两个数据,下面需要使用到mysql> select count() from article_rank;+———-+| count() |+———-+| 14146055 |+———-+mysql> select count() from article_rank where day>‘20190115’;+———-+| count() |+———-+| 3208513 |+———-+实验1因为满足条件的总行数是3208513,因为使用的是idx_day_aid_pv索引,而查询的值是aid和pv,所以是覆盖索引,不需要进行回表。但是可以看到在创建临时表(creating_tmp_table)之后,因为超过临时表内存限制(memory_table_size_exceeded),所以这3208513行数据的临时表会写入磁盘,使用的依然是InnoDB引擎。所以实验1最后结果是 32085132 + 1 = 6417027;实验2相比实验1,实验2中不仅需要对临时表存盘,同时因为索引是idx_day,不能使用覆盖索引,还需要每行都回表,所以最后结果是 32085133 + 1 = 9625540;实验3实验3中因为最左列是aid,无法对day>20190115表达式进行过滤筛选,所以需要遍历整个索引(覆盖所有行的数据)。但是本次过程中创建的临时表(memory 引擎)没有写入磁盘,都是在内存中操作,所以最后结果是14146055 + 1 = 14146056;需要注意,如果我们开启慢查询日志,慢查询日志里面的扫描行数和这里统计的不一样,内存临时表的扫描行数也算在内的。耗时也是最短的。为什么实验3使用的是 rowid 排序而不是 additional_fields 排序? 同样是写入 649091 到内存临时表,为什么其他三种方式都会出现内存不够用的情况呢?莫非其他三种情况是先把所有的行写入到临时表,再遍历合并?实验4实验4首先遍历主表,需要扫描14146055行,然后把符合条件的3208513行放入临时表 ,所以最后是14146055 + 3208513 + 1 = 17354569。参考《MySQL实战45讲》https://time.geekbang.org/column/article/73479https://time.geekbang.org/column/article/73795https://dev.mysql.com/doc/ref…https://juejin.im/entry/59019… ...

February 13, 2019 · 6 min · jiezi

MySQL慢日志实践

慢日志查询作用慢日志查询的主要功能就是,记录sql语句中超过设定的时间阈值的查询语句。例如,一条查询sql语句,我们设置的阈值为1s,当这条查询语句的执行时间超过了1s,则将被写入到慢查询配置的日志中.慢查询主要是为了我们做sql语句的优化功能.慢查询配置项说明登录mysql服务,使用如下命令mysql> show variables like ‘%query%’;+——————————+—————————————–+| Variable_name | Value |+——————————+—————————————–+| binlog_rows_query_log_events | OFF || ft_query_expansion_limit | 20 || have_query_cache | YES || long_query_time | 10.000000 || query_alloc_block_size | 8192 || query_cache_limit | 1048576 || query_cache_min_res_unit | 4096 || query_cache_size | 33554432 || query_cache_type | OFF || query_cache_wlock_invalidate | OFF || query_prealloc_size | 8192 || slow_query_log | OFF || slow_query_log_file | /usr/local/mysql/var/localhost-slow.log |+——————————+—————————————–+13 rows in set (0.01 sec)这里,我们只需要关注三个配置项即可。1.slow_query_log该配置项是决定是否开启慢日志查询功能,配置的值有ON或者OFF.2.slow_query_log_file该配置项是慢日志查询的记录文件,需要手动创建.3.long_query_time该配置项是设置慢日志查询的时间阈值,当超过这个阈值时,慢日志才会被记录.配置的值有0(任何的sql语句都记录下来),或者>0(具体的阈值).该配置项是以秒为单位的,并且可以设置为小数.4.log-queries-not-using-indexes该配置项是为了记录未使用到索引的sql语句.如何配置慢查询配置慢查询功能的方式有两种,一种是使用mysql的配置文件配置,另外一种是使用mysql命令配置.这里建议使用配置文件配置,因为在命令配置的过程中发现有时候配置项在set命令的时候是成功了,但是查询还是没设置。1.配置文件配置// 找到[mysqld],在其下面添加如下代码即可.slow_query_log=ONslow_query_log_file=/usr/local/mysql/var/localhost-slow.loglong_query_time=0log-queries-not-using-indexes = 1// 配置好后,重启mysql服务2.使用命令配置// 这里就简单些一个配置项就行了,其他的配置项均按照此方法配置mysql> set slow_query_log=ON;配置好之后,查看mysql慢查询日志是否配置成功.mysql> show variables like ‘%query%’;+——————————+—————————————–+| Variable_name | Value |+——————————+—————————————–+| binlog_rows_query_log_events | OFF || ft_query_expansion_limit | 20 || have_query_cache | YES || long_query_time | 0.000000 || query_alloc_block_size | 8192 || query_cache_limit | 1048576 || query_cache_min_res_unit | 4096 || query_cache_size | 33554432 || query_cache_type | OFF || query_cache_wlock_invalidate | OFF || query_prealloc_size | 8192 || slow_query_log | ON || slow_query_log_file | /usr/local/mysql/var/localhost-slow.log |+——————————+—————————————–+13 rows in set (0.01 sec)如何查看慢日志记录在配置慢查询之前,我们已经导入了示例的数据文件,这里就不做单独的演示了。mysql官方数据库示例 。接下来,我们就开始做查询操作.mysql> select * from city where city=‘Salala’;+———+——–+————+———————+| city_id | city | country_id | last_update |+———+——–+————+———————+| 444 | Salala | 71 | 2006-02-15 04:45:25 |+———+——–+————+———————+1 row in set (0.01 sec)此时,我们根据配置的慢查询日志记录文件/usr/local/mysql/var/localhost-slow.log,发现该文件记录了上面的命令操作.# Time: 2019-01-17T08:12:27.184998Z# User@Host: root[root] @ localhost [] Id: 4# Query_time: 0.002773 Lock_time: 0.001208 Rows_sent: 1 Rows_examined: 600SET timestamp=1547712747;select * from city where city=‘Salala’;上诉文件配置内容说明1.Time该日志记录的时间2.User@HostMySQL登录的用户和登录的主机地址3.Query_time一行第一个时间是查询的时间、第二个是锁表的时间、第三个是返回的行数、第四个是扫描的行数4.SET timestamp这一个是MySQL查询的时间5.sql语句这一行就很明显了,表示的是我们执行的sql语句总结由于我们配置long_query_time=0,因此所有的sql语句都将被记录下来,这里我们假设,仅仅是假设。我们设置的long_query_time=5,然而上面的第三项中Query_time大于5,如果是实际项目中不属于正常范围,则需要对其进行优化,当然优化的方式有很多种,下面我们使用简单的索引方式进行优化一下。优化sql语句1.先查看原本的sql语句执行结构mysql> explain select * from city where city=‘Salala’\G;*************************** 1. row *************************** id: 1 select_type: SIMPLE table: city partitions: NULL type: ALLpossible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 600 filtered: 10.00 Extra: Using where1 row in set, 1 warning (0.00 sec)mysql> create index idx_cityName on city(city);Query OK, 0 rows affected (0.03 sec)Records: 0 Duplicates: 0 Warnings: 0看得出,该sql语句是进行了全盘扫描。我们则用索引对其简单的优化一下。2.创建索引mysql> create index idx_cityName on city(city);Query OK, 0 rows affected (0.03 sec)Records: 0 Duplicates: 0 Warnings: 03.在用explain分析一次mysql> explain select * from city where city=‘Salala’\G;*************************** 1. row *************************** id: 1 select_type: SIMPLE table: city partitions: NULL type: refpossible_keys: idx_cityName key: idx_cityName key_len: 152 ref: const rows: 1 filtered: 100.00 Extra: NULL1 row in set, 1 warning (0.00 sec)通过创建索引,我们发现此时只扫描了一行,并且是使用的索引扫描,因此大大提高了一个mysql查询的效率。MySQL慢查询使用总结在平常的开发中,慢查询作为MySQL优化的一个途径,是非常有用的。它会记录下我们一些查询时间长的sql语句,对其语句我们进行分析,从而达到sql查询语句的最优化。但是慢日志查询开启之后,针对sql查询会通过磁盘I/O将相关的记录写入到磁盘文件中,增加了一个磁盘的I/O读写。因此,我们该功能用在开发、测试环境上,而不用在生产环境中去。慢日志查询工具由于慢日志查询文件越到后期,内容越多。我们对其分析的压力越大,因此我们需要借助某些工具实现快速分析。这些工具还没完全使用熟悉,后期单独写一篇文章介绍该类型的工具,这里只是罗列一下工具名称。1.mysqldumpslow2.pt-query-digest3.mysqltop(天兔Lepus)原文转自浪子编程走四方 ...

January 17, 2019 · 2 min · jiezi

如何判断mysql是否命中缓存

mysql的查询缓存是在完整的select语句基础上判断的,而且只是在刚收到SQL语句的时候才检查,所以子查询和存储过程都没办法使用查询缓存。如果查询语句中包含任何的不确定因素或者函数,那么就不会命中查询缓存,不确定因素指客户端协议的版本等,而不确定函数指now()这种函数,它随着时间变化而变化,是不确定的。在一个事务提交之前表的相关查询是不走缓存的。打个比喻就是mysql将你的整个sql语句原封不动地先检查是否有不确定函数,如果有不确定函数就不走缓存,如果没有不确定函数,那么将sql语句做个md5计算,如果找得到计算的值就走缓存,否则就不走缓存。为了用上查询缓存,优化的思路有下面这些:1.将子查询或者联结拆开成单表的查询,因为整个sql语句只要有不确定的函数就不走缓存,拆开后其中的某些查询可能就走缓存了。2.将不确定函数替换成确定的值,比如你用系统函数current_date()生成的日期就可以用提前计算好的日期替换掉。

January 13, 2019 · 1 min · jiezi

Mysql事务隔离级别之可重复读

Mysql事务隔离级别之。可重复读(REPEATABLE-READ)查看mysql 事务隔离级别mysql> SELECT @@tx_isolation;+—————–+| @@tx_isolation |+—————–+| REPEATABLE-READ |+—————–+1 row in set (0.00 sec)可以看到默认的事务隔离级别为 REPEATABLE-READ 可重复读下面看看当前隔离级别下的事务隔离详情,开启两个查询终端A、B。下面有一个order表,初始数据如下mysql> select * from order;+—-+——–+| id | number |+—-+——–+| 13 | 1 |+—-+——–+1 row in set (0.00 sec)第一步,在A,B中都开启事务mysql> start transaction;Query OK, 0 rows affected (0.00 sec)第二步查询两个终端中的number值A mysql> select * from order;+—-+——–+| id | number |+—-+——–+| 13 | 1 |+—-+——–+1 row in set (0.00 sec)B mysql> select * from order;+—-+——–+| id | number |+—-+——–+| 13 | 1 |+—-+——–+1 row in set (0.00 sec)第三步将B中的number修改为2,但不提交事务mysql> update order set number=2;Query OK, 1 row affected (0.00 sec)Rows matched: 1 Changed: 1 Warnings: 0第四步查询A中的值mysql> select * from order;+—-+——–+| id | number |+—-+——–+| 13 | 1 |+—-+——–+1 row in set (0.00 sec)发现A中的值并没有修改。第五步,提交事务B,再次查询A中的值Bmysql> commit;Query OK, 0 rows affected (0.01 sec)Amysql> select * from order;+—-+——–+| id | number |+—-+——–+| 13 | 1 |+—-+——–+1 row in set (0.00 sec)发现A中的值还是未更改第六步,提交A中的事务,再次查询A,B的值。Amysql> commit;Query OK, 0 rows affected (0.00 sec)mysql> select * from order;+—-+——–+| id | number |+—-+——–+| 13 | 2 |+—-+——–+1 row in set (0.00 sec)Bmysql> select * from order;+—-+——–+| id | number |+—-+——–+| 13 | 2 |+—-+——–+1 row in set (0.00 sec)发现A,B中的值都更改为2了。下面给一个简单的示意图我们可以看到,在事务隔离级别为可重复读 的情况下,A,B事务在执行期间前后看到的数据是一致的。这也就是可重复读的隔离特性。·遵循事务在执行期间看到的数据前后必须是一致的`原文地址 ...

January 4, 2019 · 1 min · jiezi

MySQL 索引及查询优化总结

本文由云+社区发表文章《MySQL查询分析》讲述了使用MySQL慢查询和explain命令来定位mysql性能瓶颈的方法,定位出性能瓶颈的sql语句后,则需要对低效的sql语句进行优化。本文主要讨论MySQL索引原理及常用的sql查询优化。一个简单的对比测试前面的案例中,c2c_zwdb.t_file_count表只有一个自增id,FFileName字段未加索引的sql执行情况如下:在上图中,type=all,key=null,rows=33777。该sql未使用索引,是一个效率非常低的全表扫描。如果加上联合查询和其他一些约束条件,数据库会疯狂的消耗内存,并且会影响前端程序的执行。这时给FFileName字段添加一个索引:alter table c2c_zwdb.t_file_count add index index_title(FFileName);再次执行上述查询语句,其对比很明显:在该图中,type=ref,key=索引名(index_title),rows=1。该sql使用了索引index_title,且是一个常数扫描,根据索引只扫描了一行。比起未加索引的情况,加了索引后,查询效率对比非常明显。MySQL索引通过上面的对比测试可以看出,索引是快速搜索的关键。MySQL索引的建立对于MySQL的高效运行是很重要的。对于少量的数据,没有合适的索引影响不是很大,但是,当随着数据量的增加,性能会急剧下降。如果对多列进行索引(组合索引),列的顺序非常重要,MySQL仅能对索引最左边的前缀进行有效的查找。下面介绍几种常见的MySQL索引类型。索引分单列索引和组合索引。单列索引,即一个索引只包含单个列,一个表可以有多个单列索引,但这不是组合索引。组合索引,即一个索引包含多个列。1、MySQL索引类型(1) 主键索引 PRIMARY KEY它是一种特殊的唯一索引,不允许有空值。一般是在建表的时候同时创建主键索引。当然也可以用 ALTER 命令。记住:一个表只能有一个主键。(2) 唯一索引 UNIQUE唯一索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。可以在创建表的时候指定,也可以修改表结构,如:ALTER TABLE table_name ADD UNIQUE (column)(3) 普通索引 INDEX这是最基本的索引,它没有任何限制。可以在创建表的时候指定,也可以修改表结构,如:ALTER TABLE table_name ADD INDEX index_name (column)(4) 组合索引 INDEX组合索引,即一个索引包含多个列。可以在创建表的时候指定,也可以修改表结构,如:ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3)(5) 全文索引 FULLTEXT全文索引(也称全文检索)是目前搜索引擎使用的一种关键技术。它能够利用分词技术等多种算法智能分析出文本文字中关键字词的频率及重要性,然后按照一定的算法规则智能地筛选出我们想要的搜索结果。可以在创建表的时候指定,也可以修改表结构,如:ALTER TABLE table_name ADD FULLTEXT (column)2、索引结构及原理mysql中普遍使用B+Tree做索引,但在实现上又根据聚簇索引和非聚簇索引而不同,本文暂不讨论这点。b+树介绍下面这张b+树的图片在很多地方可以看到,之所以在这里也选取这张,是因为觉得这张图片可以很好的诠释索引的查找过程。如上图,是一颗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,显然成本非常非常高。性质(1) 索引字段要尽量的小。通过上面b+树的查找过程,或者通过真实的数据存在于叶子节点这个事实可知,IO次数取决于b+数的高度h。假设当前数据表的数据量为N,每个磁盘块的数据项的数量是m,则树高h=㏒(m+1)N,当数据量N一定的情况下,m越大,h越小;而m = 磁盘块的大小/数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的;如果数据项占的空间越小,数据项的数量m越多,树的高度h越低。这就是为什么每个数据项,即索引字段要尽量的小,比如int占4字节,要比bigint8字节少一半。(2) 索引的最左匹配特性。当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。建索引的几大原则(1) 最左前缀匹配原则对于多列索引,总是从索引的最前面字段开始,接着往后,中间不能跳过。比如创建了多列索引(name,age,sex),会先匹配name字段,再匹配age字段,再匹配sex字段的,中间不能跳过。mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配。一般,在创建多列索引时,where子句中使用最频繁的一列放在最左边。看一个补符合最左前缀匹配原则和符合该原则的对比例子。实例:表c2c_db.t_credit_detail建有索引(Flistid,Fbank_listid)不符合最左前缀匹配原则的sql语句:select * from t_credit_detail where Fbank_listid=‘201108010000199’G该sql直接用了第二个索引字段Fbank_listid,跳过了第一个索引字段Flistid,不符合最左前缀匹配原则。用explain命令查看sql语句的执行计划,如下图:从上图可以看出,该sql未使用索引,是一个低效的全表扫描。符合最左前缀匹配原则的sql语句:select * from t_credit_detail where Flistid=‘2000000608201108010831508721’ and Fbank_listid=‘201108010000199’G该sql先使用了索引的第一个字段Flistid,再使用索引的第二个字段Fbank_listid,中间没有跳过,符合最左前缀匹配原则。用explain命令查看sql语句的执行计划,如下图:从上图可以看出,该sql使用了索引,仅扫描了一行。对比可知,符合最左前缀匹配原则的sql语句比不符合该原则的sql语句效率有极大提高,从全表扫描上升到了常数扫描。(2) 尽量选择区分度高的列作为索引。比如,我们会选择学号做索引,而不会选择性别来做索引。(3) =和in可以乱序比如a = 1 and b = 2 and c = 3,建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式。(4) 索引列不能参与计算,保持列“干净”比如:Flistid+1>‘2000000608201108010831508721‘。原因很简单,假如索引列参与计算的话,那每次检索时,都会先将索引计算一次,再做比较,显然成本太大。(5) 尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。索引的不足虽然索引可以提高查询效率,但索引也有自己的不足之处。索引的额外开销:(1) 空间:索引需要占用空间;(2) 时间:查询索引需要时间;(3) 维护:索引须要维护(数据变更时);不建议使用索引的情况:(1) 数据量很小的表(2) 空间紧张常用优化总结优化语句很多,需要注意的也很多,针对平时的情况总结一下几点:1、有索引但未被用到的情况(不建议)(1) Like的参数以通配符开头时尽量避免Like的参数以通配符开头,否则数据库引擎会放弃使用索引而进行全表扫描。以通配符开头的sql语句,例如:select * from t_credit_detail where Flistid like ‘%0’G这是全表扫描,没有使用到索引,不建议使用。不以通配符开头的sql语句,例如:select * from t_credit_detail where Flistid like ‘2%‘G很明显,这使用到了索引,是有范围的查找了,比以通配符开头的sql语句效率提高不少。(2) where条件不符合最左前缀原则时例子已在最左前缀匹配原则的内容中有举例。(3) 使用!= 或 <> 操作符时尽量避免使用!= 或 <>操作符,否则数据库引擎会放弃使用索引而进行全表扫描。使用>或<会比较高效。select * from t_credit_detail where Flistid != ‘2000000608201108010831508721’G(4) 索引列参与计算应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。select * from t_credit_detail where Flistid +1 > ‘2000000608201108010831508722’G(5) 对字段进行null值判断应尽量避免在where子句中对字段进行null值判断,否则将导致引擎放弃使用索引而进行全表扫描,如: 低效:select * from t_credit_detail where Flistid is null ;可以在Flistid上设置默认值0,确保表中Flistid列没有null值,然后这样查询: 高效:select * from t_credit_detail where Flistid =0;(6) 使用or来连接条件应尽量避免在where子句中使用or来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如: 低效:select * from t_credit_detail where Flistid = ‘2000000608201108010831508721’ or Flistid = ‘10000200001’;可以用下面这样的查询代替上面的 or 查询: 高效:select from t_credit_detail where Flistid = ‘2000000608201108010831508721’ union all select from t_credit_detail where Flistid = ‘10000200001’;2、避免select 在解析的过程中,会将’’ 依次转换成所有的列名,这个工作是通过查询数据字典完成的,这意味着将耗费更多的时间。所以,应该养成一个需要什么就取什么的好习惯。3、order by 语句优化任何在Order by语句的非索引项或者有计算表达式都将降低查询速度。方法:1.重写order by语句以使用索引; 2.为所使用的列建立另外一个索引 3.绝对避免在order by子句中使用表达式。4、GROUP BY语句优化提高GROUP BY 语句的效率, 可以通过将不需要的记录在GROUP BY 之前过滤掉低效:SELECT JOB , AVG(SAL)FROM EMPGROUP by JOBHAVING JOB = ‘PRESIDENT’OR JOB = ‘MANAGER’高效:SELECT JOB , AVG(SAL)FROM EMPWHERE JOB = ‘PRESIDENT’OR JOB = ‘MANAGER’GROUP by JOB5、用 exists 代替 in很多时候用 exists 代替 in 是一个好的选择: select num from a where num in(select num from b) 用下面的语句替换: select num from a where exists(select 1 from b where num=a.num)6、使用 varchar/nvarchar 代替 char/nchar尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。7、能用DISTINCT的就不用GROUP BYSELECT OrderID FROM Details WHERE UnitPrice > 10 GROUP BY OrderID可改为:SELECT DISTINCT OrderID FROM Details WHERE UnitPrice > 108、能用UNION ALL就不要用UNIONUNION ALL不执行SELECT DISTINCT函数,这样就会减少很多不必要的资源。9、在Join表的时候使用相当类型的例,并将其索引如果应用程序有很多JOIN 查询,你应该确认两个表中Join的字段是被建过索引的。这样,MySQL内部会启动为你优化Join的SQL语句的机制。而且,这些被用来Join的字段,应该是相同的类型的。例如:如果你要把 DECIMAL 字段和一个 INT 字段Join在一起,MySQL就无法使用它们的索引。对于那些STRING类型,还需要有相同的字符集才行。(两个表的字符集有可能不一样)此文已由作者授权腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号 ...

January 2, 2019 · 2 min · jiezi

举个栗子看如何做MySQL 内核深度优化

本文由云+社区发表作者介绍:简怀兵,腾讯云数据库高级工程师,负责腾讯云CDB内核及基础设施建设;先后供职于Thomson Reuters和YY等公司,PTimeDB作者,曾获一项发明专利;从事MySQL内核开发工作8年,具有丰富的优化经验;在分布式存储等领域有较丰富经验。MYSQL数据库适用场景广泛,相较于Oracle、DB2性价比更高,Web网站、日志系统、数据仓库等场景都有MYSQL用武之地,但是也存在对于事务性支持不太好(MySQL 5.5版本开始默认引擎才是InnoDB事务型)、存在多个分支、读写效率瓶颈等问题。所以如何用好MYSQL变得至关重要,一方面需要通过MYSQL优化找出系统读写瓶颈,提高数据库性能;另一方面需要合理涉及数据结构、调整参数,以提高用户操作响应;同时还有尽可能节省系统资源,以便系统可以提供更大负荷的服务。本文将为大家介绍腾讯云团队是如何对Mysql进行内核级优化的思路和经验。早期的CDB主要基于开源的Oracle MySQL分支,侧重于优化运维和运营的OSS系统。在腾讯云,因为用户数的不断增加,对CDB for MySQL提出越来越高的要求,腾讯云CDB团队针对用户的需求和业界发展的技术趋势,对CDB for MySQL分支进行深度的定制优化。优化重点围绕内核性能、内核功能和外围OSS系统三个维度展开,具体的做法如下:一.内核性能的优化由于腾讯云上的DB基本都需要跨园区灾备的特性,因此CDB for MySQL的优化主要针对主从DB部署在跨园区网络拓扑的前提下,重点去解决真实部署环境下的性能难题。经过分析和调研,我们将优化的思路归纳为:“消除冗余I/O、缩短I/O路径和避免大锁竞争”。以下是内核性能的部分案例:1.主备DB间的复制优化问题分析如上图所示,在原生MySQL的复制架构中,Master侧通过Dump线程不断发送Binlog事件给Slave的I/O线程,Slave的I/O线程在接受到Binlog事件后,有两个主要的动作:写入到Relay Log中,这个过程会和Slave SQL线程争抢保护Relay Log的锁。更新复制元数据(包含Master的位置等信息)。优化方法经过分析,我们的优化策略是:Slave I/O线程和Slave SQL线程是典型的单写单读生产者-消费者模型,是可以做到无锁设计的;因此实现思路就是Slave I/O线程在每次写完数据后,原子更新Relay Log的长度信息,Slave SQL线程读取Relay Log的时以长度信息为边界。这样就将原本竞争激烈的Relay Log锁化解为无锁;由于Binlog事件中的GTID(Global Transaction Identifier)和DB事务是一一对应的关系,所以Relay Log中的数据本身已经包含了所需要的复制元数据,所以我们可以不写Master info文件,消除了冗余的文件I/O;于DB都是以事务为更新粒度的,因为在Relay Log文件I/O上,我们通过合并离散小I/O为事务粒度的大I/O等手段,使磁盘I/O得以大幅提升。优化效果如上图所示,经过优化:左图35.79%的锁竞争(futex)已经被完全消除;同压测压力下,56.15%的文件I/O开销被优化到19.16%,Slave I/O线程被优化为预期的I/O密集型线程。2.主库事务线程和Dump线程间的优化问题分析如上图所示,在原生MySQL中多个事务提交线程TrxN和多个Dump线程之间会同时竞争Binlog文件资源的保护锁,多个事务提交线程对Binlog执行写入,多个Dump线程从Binlog文件读取数据并发送给Slave。所有的线程之间是串行执行的!优化方法经过分析,我们的优化策略是:将读写分离开来,多个写入的线程还是在锁保护下串行执行,每一个写入线程写入完成后更新当前Binlog的长度信息,多个Dump线程以Binlog文件的长度信息为读取边界,多个Dump线程之间并行执行。以这种方式来让复制拓扑中的Dump线程发送得更快!效果经过测试,优化后的内核,不仅提升了事务提交线程的性能,在Dump线程较多的情况下,对主从复制性能有较大提升。二.主备库交互流程优化问题分析如上图所示,在原生MySQL中主备库之间的数据发送和ACK回应是简单的串行执行,在上一个事件ACK回应到达之前,不允许继续发送下一个事件;这个行为在跨园区(RTT 2-3ms)的情况性能非常差,而且也不能很好地利用带宽优势。优化方法经过分析,我们的优化策略是:将发送和ACK回应的接收独立到不同的线程中,由于发送和接收都是基于TCP流的传输,所以时序性是有保障的;这样发送线程可以在未收ACK之前继续发送,接受线程收到ACK后唤醒等待的线程执行相应的任务。效果根据实际用例测试,优化后的TPS提升为15%左右。三.内核功能的优化1. 预留运维帐号连接数配额在腾讯云上,不时遇到用户APP异常或者BUG从而占满DB的最大连接限制,这是CDB OSS帐号无法登录以进行紧急的运维操作。针对这个现状,我们在MySQL内核单独开辟了一个可配置的连接数配额,即便在上述场景下,运维帐号仍然可以连接到DB进行紧急的运维操作。极大地降低了异常情况下DB无政府状态的风险。该帐号仅有数据库运维管理权限,无法获取用户数据,也保证了用户数据的安全性。2. 主备强同步针对一些应用对数据的一致性要求非常高,CDB在MySQL原生半同步的基础上进行了深度优化,确保一个事务在主库上提交之前一定已经复制到至少一个备库上。确保主库宕机时数据的一致性。四.外围系统的优化除了以上提到的MySQL内核侧的部分优化,我们也在外围OSS平台进行了多处优化。例如使用异步MySQL ping协议实现大量实例的监控、通过分布式技术来加固原有系统的HA/服务发现和自动扩容等功能、在数据安全/故障切换和快速恢复方面也进行了多处优化。此文已由作者授权腾讯云+社区发布

December 25, 2018 · 1 min · jiezi

MySQL索引原理及慢查询优化

背景MySQL凭借着出色的性能、低廉的成本、丰富的资源,已经成为绝大多数互联网公司的首选关系型数据库。虽然性能出色,但所谓“好马配好鞍”,如何能够更好的使用它,已经成为开发工程师的必修课,我们经常会从职位描述上看到诸如“精通MySQL”、“SQL语句优化”、“了解数据库原理”等要求。我们知道一般的应用系统,读写比例在10:1左右,而且插入操作和一般的更新操作很少出现性能问题,遇到最多的,也是最容易出问题的,还是一些复杂的查询操作,所以查询语句的优化显然是重中之重。本人从13年7月份起,一直在美团核心业务系统部做慢查询的优化工作,共计十余个系统,累计解决和积累了上百个慢查询案例。随着业务的复杂性提升,遇到的问题千奇百怪,五花八门,匪夷所思。本文旨在以开发工程师的角度来解释数据库索引的原理和如何优化慢查询。一个慢查询引发的思考select count() from task where status=2 and operator_id=20839 and operate_time>1371169729 and operate_time<1371174603 and type=2;系统使用者反应有一个功能越来越慢,于是工程师找到了上面的SQL。并且兴致冲冲的找到了我,“这个SQL需要优化,给我把每个字段都加上索引”。我很惊讶,问道:“为什么需要每个字段都加上索引?”“把查询的字段都加上索引会更快”,工程师信心满满。“这种情况完全可以建一个联合索引,因为是最左前缀匹配,所以operate_time需要放到最后,而且还需要把其他相关的查询都拿来,需要做一个综合评估。”“联合索引?最左前缀匹配?综合评估?”工程师不禁陷入了沉思。多数情况下,我们知道索引能够提高查询效率,但应该如何建立索引?索引的顺序如何?许多人却只知道大概。其实理解这些概念并不难,而且索引的原理远没有想象的那么复杂。MySQL索引原理索引目的索引的目的在于提高查询效率,可以类比字典,如果要查“mysql”这个单词,我们肯定需要定位到m字母,然后从下往下找到y字母,再找到剩下的sql。如果没有索引,那么你可能需要把所有单词看一遍才能找到你想要的,如果我想找到m开头的单词呢?或者ze开头的单词呢?是不是觉得如果没有索引,这个事情根本无法完成?索引原理除了词典,生活中随处可见索引的例子,如火车站的车次表、图书的目录等。它们的原理都是一样的,通过不断的缩小想要获得数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是我们总是通过同一种查找方式来锁定数据。数据库也是一样,但显然要复杂许多,因为不仅面临着等值查询,还有范围查询(>、<、between、in)、模糊查询(like)、并集查询(or)等等。数据库应该选择怎么样的方式来应对所有的问题呢?我们回想字典的例子,能不能把数据分成段,然后分段查询呢?最简单的如果1000条数据,1到100分成第一段,101到200分成第二段,201到300分成第三段……这样查第250条数据,只要找第三段就可以了,一下子去除了90%的无效数据。但如果是1千万的记录呢,分成几段比较好?稍有算法基础的同学会想到搜索树,其平均复杂度是lgN,具有不错的查询性能。但这里我们忽略了一个关键的问题,复杂度模型是基于每次相同的操作成本来考虑的,数据库实现比较复杂,数据保存在磁盘上,而为了提高性能,每次又可以把部分数据读入内存来计算,因为我们知道访问磁盘的成本大概是访问内存的十万倍左右,所以简单的搜索树难以满足复杂的应用场景。磁盘IO与预读前面提到了访问磁盘,那么这里先简单介绍一下磁盘IO和预读,磁盘读取数据靠的是机械运动,每次读取数据花费的时间可以分为寻道时间、旋转延迟、传输时间三个部分,寻道时间指的是磁臂移动到指定磁道所需要的时间,主流磁盘一般在5ms以下;旋转延迟就是我们经常听说的磁盘转速,比如一个磁盘7200转,表示每分钟能转7200次,也就是说1秒钟能转120次,旋转延迟就是1/120/2 = 4.17ms;传输时间指的是从磁盘读出或将数据写入磁盘的时间,一般在零点几毫秒,相对于前两个时间可以忽略不计。那么访问一次磁盘的时间,即一次磁盘IO的时间约等于5+4.17 = 9ms左右,听起来还挺不错的,但要知道一台500 -MIPS的机器每秒可以执行5亿条指令,因为指令依靠的是电的性质,换句话说执行一次IO的时间可以执行40万条指令,数据库动辄十万百万乃至千万级数据,每次9毫秒的时间,显然是个灾难。下图是计算机硬件延迟的对比图,供大家参考:考虑到磁盘IO是非常高昂的操作,计算机操作系统做了一些优化,当一次IO时,不光把当前磁盘地址的数据,而是把相邻的数据也都读取到内存缓冲区内,因为局部预读性原理告诉我们,当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到。每一次IO读取的数据我们称之为一页(page)。具体一页有多大数据跟操作系统有关,一般为4k或8k,也就是我们读取一页内的数据时候,实际上才发生了一次IO,这个理论对于索引的数据结构设计非常有帮助。索引的数据结构前面讲了生活中索引的例子,索引的基本原理,数据库的复杂性,又讲了操作系统的相关知识,目的就是让大家了解,任何一种数据结构都不是凭空产生的,一定会有它的背景和使用场景,我们现在总结一下,我们需要这种数据结构能够做些什么,其实很简单,那就是:每次查找数据时把磁盘IO次数控制在一个很小的数量级,最好是常数数量级。那么我们就想到如果一个高度可控的多路搜索树是否能满足需求呢?就这样,b+树应运而生。详解b+树如上图,是一颗b+树,关于b+树的定义可以参见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并不真实存在于数据表中。b+树的查找过程如图所示,如果要查找数据项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,显然成本非常非常高。b+树性质1.通过上面的分析,我们知道IO次数取决于b+数的高度h,假设当前数据表的数据为N,每个磁盘块的数据项的数量是m,则有h=㏒(m+1)N,当数据量N一定的情况下,m越大,h越小;而m = 磁盘块的大小 / 数据项的大小,磁盘块的大小也就是一个数据页的大小,是固定的,如果数据项占的空间越小,数据项的数量越多,树的高度越低。这就是为什么每个数据项,即索引字段要尽量的小,比如int占4字节,要比bigint8字节少一半。这也是为什么b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度下降,导致树增高。当数据项等于1时将会退化成线性表。2.当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。慢查询优化关于MySQL索引原理是比较枯燥的东西,大家只需要有一个感性的认识,并不需要理解得非常透彻和深入。我们回头来看看一开始我们说的慢查询,了解完索引原理之后,大家是不是有什么想法呢?先总结一下索引的几大基本原则:建索引的几大原则1.最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。2.=和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式。3.尽量选择区分度高的列作为索引,区分度的公式是count(distinct col)/count(),表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,而一些状态、性别字段可能在大数据面前区分度就是0,那可能有人会问,这个比例有什么经验值吗?使用场景不同,这个值也很难确定,一般需要join的字段我们都要求是0.1以上,即平均1条扫描10条记录。4.索引列不能参与计算,保持列“干净”,比如from_unixtime(create_time) = ’2014-05-29’就不能使用到索引,原因很简单,b+树中存的都是数据表中的字段值,但进行检索时,需要把所有元素都应用函数才能比较,显然成本太大。所以语句应该写成create_time = unix_timestamp(’2014-05-29’)。5.尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。回到开始的慢查询根据最左匹配原则,最开始的sql语句的索引应该是status、operator_id、type、operate_time的联合索引;其中status、operator_id、type的顺序可以颠倒,所以我才会说,把这个表的所有相关查询都找到,会综合分析;比如还有如下查询:select * from task where status = 0 and type = 12 limit 10;select count() from task where status = 0 ;那么索引建立成(status,type,operator_id,operate_time)就是非常正确的,因为可以覆盖到所有情况。这个就是利用了索引的最左匹配的原则查询优化神器 - explain命令关于explain命令相信大家并不陌生,具体用法和字段含义可以参考官网explain-output,这里需要强调rows是核心指标,绝大部分rows小的语句执行一定很快(有例外,下面会讲到)。所以优化语句基本上都是在优化rows。慢查询优化基本步骤0.先运行看看是否真的很慢,注意设置SQL_NO_CACHE1.where条件单表查,锁定最小返回记录表。这句话的意思是把查询语句的where都应用到表中返回的记录数最小的表开始查起,单表每个字段分别查询,看哪个字段的区分度最高2.explain查看执行计划,是否与1预期一致(从锁定记录较少的表开始查询)3.order by limit 形式的sql语句让排序的表优先查4.了解业务方使用场景5.加索引时参照建索引的几大原则6.观察结果,不符合预期继续从0分析几个慢查询案例下面几个例子详细解释了如何分析和优化慢查询。复杂语句写法很多情况下,我们写SQL只是为了实现功能,这只是第一步,不同的语句书写方式对于效率往往有本质的差别,这要求我们对mysql的执行计划和索引原则有非常清楚的认识,请看下面的语句:select distinct cert.emp_id from cm_log cl inner join ( select emp.id as emp_id, emp_cert.id as cert_id from employee emp left join emp_certificate emp_cert on emp.id = emp_cert.emp_id where emp.is_deleted=0 ) cert on ( cl.ref_table=‘Employee’ and cl.ref_oid= cert.emp_id ) or ( cl.ref_table=‘EmpCertificate’ and cl.ref_oid= cert.cert_id ) where cl.last_upd_date >=‘2013-11-07 15:03:00’ and cl.last_upd_date<=‘2013-11-08 16:00:00’;0.先运行一下,53条记录 1.87秒,又没有用聚合语句,比较慢53 rows in set (1.87 sec)1.explain+—-+————-+————+——-+———————————+———————–+———+——————-+——-+——————————–+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+—-+————-+————+——-+———————————+———————–+———+——————-+——-+——————————–+| 1 | PRIMARY | cl | range | cm_log_cls_id,idx_last_upd_date | idx_last_upd_date | 8 | NULL | 379 | Using where; Using temporary || 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 63727 | Using where; Using join buffer || 2 | DERIVED | emp | ALL | NULL | NULL | NULL | NULL | 13317 | Using where || 2 | DERIVED | emp_cert | ref | emp_certificate_empid | emp_certificate_empid | 4 | meituanorg.emp.id | 1 | Using index |+—-+————-+————+——-+———————————+———————–+———+——————-+——-+——————————–+简述一下执行计划,首先mysql根据idx_last_upd_date索引扫描cm_log表获得379条记录;然后查表扫描了63727条记录,分为两部分,derived表示构造表,也就是不存在的表,可以简单理解成是一个语句形成的结果集,后面的数字表示语句的ID。derived2表示的是ID = 2的查询构造了虚拟表,并且返回了63727条记录。我们再来看看ID = 2的语句究竟做了写什么返回了这么大量的数据,首先全表扫描employee表13317条记录,然后根据索引emp_certificate_empid关联emp_certificate表,rows = 1表示,每个关联都只锁定了一条记录,效率比较高。获得后,再和cm_log的379条记录根据规则关联。从执行过程上可以看出返回了太多的数据,返回的数据绝大部分cm_log都用不到,因为cm_log只锁定了379条记录。如何优化呢?可以看到我们在运行完后还是要和cm_log做join,那么我们能不能之前和cm_log做join呢?仔细分析语句不难发现,其基本思想是如果cm_log的ref_table是EmpCertificate就关联emp_certificate表,如果ref_table是Employee就关联employee表,我们完全可以拆成两部分,并用union连接起来,注意这里用union,而不用union all是因为原语句有“distinct”来得到唯一的记录,而union恰好具备了这种功能。如果原语句中没有distinct不需要去重,我们就可以直接使用union all了,因为使用union需要去重的动作,会影响SQL性能。优化过的语句如下:select emp.id from cm_log cl inner join employee emp on cl.ref_table = ‘Employee’ and cl.ref_oid = emp.id where cl.last_upd_date >=‘2013-11-07 15:03:00’ and cl.last_upd_date<=‘2013-11-08 16:00:00’ and emp.is_deleted = 0 unionselect emp.id from cm_log cl inner join emp_certificate ec on cl.ref_table = ‘EmpCertificate’ and cl.ref_oid = ec.id inner join employee emp on emp.id = ec.emp_id where cl.last_upd_date >=‘2013-11-07 15:03:00’ and cl.last_upd_date<=‘2013-11-08 16:00:00’ and emp.is_deleted = 04.不需要了解业务场景,只需要改造的语句和改造之前的语句保持结果一致5.现有索引可以满足,不需要建索引6.用改造后的语句实验一下,只需要10ms 降低了近200倍!+—-+————–+————+——–+———————————+——————-+———+———————–+——+————-+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+—-+————–+————+——–+———————————+——————-+———+———————–+——+————-+| 1 | PRIMARY | cl | range | cm_log_cls_id,idx_last_upd_date | idx_last_upd_date | 8 | NULL | 379 | Using where || 1 | PRIMARY | emp | eq_ref | PRIMARY | PRIMARY | 4 | meituanorg.cl.ref_oid | 1 | Using where || 2 | UNION | cl | range | cm_log_cls_id,idx_last_upd_date | idx_last_upd_date | 8 | NULL | 379 | Using where || 2 | UNION | ec | eq_ref | PRIMARY,emp_certificate_empid | PRIMARY | 4 | meituanorg.cl.ref_oid | 1 | || 2 | UNION | emp | eq_ref | PRIMARY | PRIMARY | 4 | meituanorg.ec.emp_id | 1 | Using where || NULL | UNION RESULT | <union1,2> | ALL | NULL | NULL | NULL | NULL | NULL | |+—-+————–+————+——–+———————————+——————-+———+———————–+——+————-+53 rows in set (0.01 sec)明确应用场景举这个例子的目的在于颠覆我们对列的区分度的认知,一般上我们认为区分度越高的列,越容易锁定更少的记录,但在一些特殊的情况下,这种理论是有局限性的。select * from stage_poi sp where sp.accurate_result=1 and ( sp.sync_status=0 or sp.sync_status=2 or sp.sync_status=4 );0.先看看运行多长时间,951条数据6.22秒,真的很慢。951 rows in set (6.22 sec)1.先explain,rows达到了361万,type = ALL表明是全表扫描。+—-+————-+——-+——+—————+——+———+——+———+————-+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+—-+————-+——-+——+—————+——+———+——+———+————-+| 1 | SIMPLE | sp | ALL | NULL | NULL | NULL | NULL | 3613155 | Using where |+—-+————-+——-+——+—————+——+———+——+———+————-+2.所有字段都应用查询返回记录数,因为是单表查询 0已经做过了951条。3.让explain的rows 尽量逼近951。看一下accurate_result = 1的记录数:select count(),accurate_result from stage_poi group by accurate_result;+———-+—————–+| count() | accurate_result |+———-+—————–+| 1023 | -1 || 2114655 | 0 || 972815 | 1 |+———-+—————–+我们看到accurate_result这个字段的区分度非常低,整个表只有-1,0,1三个值,加上索引也无法锁定特别少量的数据。再看一下sync_status字段的情况:select count(),sync_status from stage_poi group by sync_status;+———-+————-+| count() | sync_status |+———-+————-+| 3080 | 0 || 3085413 | 3 |+———-+————-+同样的区分度也很低,根据理论,也不适合建立索引。问题分析到这,好像得出了这个表无法优化的结论,两个列的区分度都很低,即便加上索引也只能适应这种情况,很难做普遍性的优化,比如当sync_status 0、3分布的很平均,那么锁定记录也是百万级别的。4.找业务方去沟通,看看使用场景。业务方是这么来使用这个SQL语句的,每隔五分钟会扫描符合条件的数据,处理完成后把sync_status这个字段变成1,五分钟符合条件的记录数并不会太多,1000个左右。了解了业务方的使用场景后,优化这个SQL就变得简单了,因为业务方保证了数据的不平衡,如果加上索引可以过滤掉绝大部分不需要的数据。5.根据建立索引规则,使用如下语句建立索引alter table stage_poi add index idx_acc_status(accurate_result,sync_status);6.观察预期结果,发现只需要200ms,快了30多倍。952 rows in set (0.20 sec)我们再来回顾一下分析问题的过程,单表查询相对来说比较好优化,大部分时候只需要把where条件里面的字段依照规则加上索引就好,如果只是这种“无脑”优化的话,显然一些区分度非常低的列,不应该加索引的列也会被加上索引,这样会对插入、更新性能造成严重的影响,同时也有可能影响其它的查询语句。所以我们第4步调差SQL的使用场景非常关键,我们只有知道这个业务场景,才能更好地辅助我们更好的分析和优化查询语句。无法优化的语句select c.id, c.name, c.position, c.sex, c.phone, c.office_phone, c.feature_info, c.birthday, c.creator_id, c.is_keyperson, c.giveup_reason, c.status, c.data_source, from_unixtime(c.created_time) as created_time, from_unixtime(c.last_modified) as last_modified, c.last_modified_user_id from contact c inner join contact_branch cb on c.id = cb.contact_id inner join branch_user bu on cb.branch_id = bu.branch_id and bu.status in ( 1, 2) inner join org_emp_info oei on oei.data_id = bu.user_id and oei.node_left >= 2875 and oei.node_right <= 10802 and oei.org_category = - 1 order by c.created_time desc limit 0 , 10;还是几个步骤。0.先看语句运行多长时间,10条记录用了13秒,已经不可忍受。10 rows in set (13.06 sec)1.explain+—-+————-+——-+——–+————————————-+————————-+———+————————–+——+———————————————-+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+—-+————-+——-+——–+————————————-+————————-+———+————————–+——+———————————————-+| 1 | SIMPLE | oei | ref | idx_category_left_right,idx_data_id | idx_category_left_right | 5 | const | 8849 | Using where; Using temporary; Using filesort || 1 | SIMPLE | bu | ref | PRIMARY,idx_userid_status | idx_userid_status | 4 | meituancrm.oei.data_id | 76 | Using where; Using index || 1 | SIMPLE | cb | ref | idx_branch_id,idx_contact_branch_id | idx_branch_id | 4 | meituancrm.bu.branch_id | 1 | || 1 | SIMPLE | c | eq_ref | PRIMARY | PRIMARY | 108 | meituancrm.cb.contact_id | 1 | |+—-+————-+——-+——–+————————————-+————————-+———+————————–+——+———————————————-+从执行计划上看,mysql先查org_emp_info表扫描8849记录,再用索引idx_userid_status关联branch_user表,再用索引idx_branch_id关联contact_branch表,最后主键关联contact表。rows返回的都非常少,看不到有什么异常情况。我们在看一下语句,发现后面有order by + limit组合,会不会是排序量太大搞的?于是我们简化SQL,去掉后面的order by 和 limit,看看到底用了多少记录来排序。select count()from contact c inner join contact_branch cb on c.id = cb.contact_id inner join branch_user bu on cb.branch_id = bu.branch_id and bu.status in ( 1, 2) inner join org_emp_info oei on oei.data_id = bu.user_id and oei.node_left >= 2875 and oei.node_right <= 10802 and oei.org_category = - 1 +———-+| count(*) |+———-+| 778878 |+———-+1 row in set (5.19 sec)发现排序之前居然锁定了778878条记录,如果针对70万的结果集排序,将是灾难性的,怪不得这么慢,那我们能不能换个思路,先根据contact的created_time排序,再来join会不会比较快呢?于是改造成下面的语句,也可以用straight_join来优化:selectc.id,c.name,c.position,c.sex,c.phone,c.office_phone,c.feature_info,c.birthday,c.creator_id,c.is_keyperson,c.giveup_reason,c.status,c.data_source,from_unixtime(c.created_time) as created_time,from_unixtime(c.last_modified) as last_modified,c.last_modified_user_idfromcontact cwhereexists (select1fromcontact_branch cbinner joinbranch_user buon cb.branch_id = bu.branch_idand bu.status in (1,2)inner joinorg_emp_info oeion oei.data_id = bu.user_idand oei.node_left >= 2875and oei.node_right <= 10802and oei.org_category = - 1wherec.id = cb.contact_id)order byc.created_time desc limit 0 ,10;验证一下效果 预计在1ms内,提升了13000多倍!10 rows in set (0.00 sec)本以为至此大工告成,但我们在前面的分析中漏了一个细节,先排序再join和先join再排序理论上开销是一样的,为何提升这么多是因为有一个limit!大致执行过程是:mysql先按索引排序得到前10条记录,然后再去join过滤,当发现不够10条的时候,再次去10条,再次join,这显然在内层join过滤的数据非常多的时候,将是灾难的,极端情况,内层一条数据都找不到,mysql还傻乎乎的每次取10条,几乎遍历了这个数据表!用不同参数的SQL试验下:select sql_no_cache c.id, c.name, c.position, c.sex, c.phone, c.office_phone, c.feature_info, c.birthday, c.creator_id, c.is_keyperson, c.giveup_reason, c.status, c.data_source, from_unixtime(c.created_time) as created_time, from_unixtime(c.last_modified) as last_modified, c.last_modified_user_id from contact c where exists ( select 1 from contact_branch cb inner join branch_user bu on cb.branch_id = bu.branch_id and bu.status in ( 1, 2) inner join org_emp_info oei on oei.data_id = bu.user_id and oei.node_left >= 2875 and oei.node_right <= 2875 and oei.org_category = - 1 where c.id = cb.contact_id ) order by c.created_time desc limit 0 , 10;Empty set (2 min 18.99 sec)2 min 18.99 sec!比之前的情况还糟糕很多。由于mysql的nested loop机制,遇到这种情况,基本是无法优化的。这条语句最终也只能交给应用系统去优化自己的逻辑了。通过这个例子我们可以看到,并不是所有语句都能优化,而往往我们优化时,由于SQL用例回归时落掉一些极端情况,会造成比原来还严重的后果。所以,第一:不要指望所有语句都能通过SQL优化,第二:不要过于自信,只针对具体case来优化,而忽略了更复杂的情况。慢查询的案例就分析到这儿,以上只是一些比较典型的案例。我们在优化过程中遇到过超过1000行,涉及到16个表join的“垃圾SQL”,也遇到过线上线下数据库差异导致应用直接被慢查询拖死,也遇到过varchar等值比较没有写单引号,还遇到过笛卡尔积查询直接把从库搞死。再多的案例其实也只是一些经验的积累,如果我们熟悉查询优化器、索引的内部原理,那么分析这些案例就变得特别简单了。写在后面的话本文以一个慢查询案例引入了MySQL索引原理、优化慢查询的一些方法论;并针对遇到的典型案例做了详细的分析。其实做了这么长时间的语句优化后才发现,任何数据库层面的优化都抵不上应用系统的优化,同样是MySQL,可以用来支撑Google/FaceBook/Taobao应用,但可能连你的个人网站都撑不住。套用最近比较流行的话:“查询容易,优化不易,且写且珍惜!”参考文献:1.《高性能MySQL》2.《数据结构与算法分析》 ...

November 24, 2018 · 5 min · jiezi