本文的内容是总结一些MySQL的常见使用技巧,以供没有DBA的团队参考。如无特殊说明,存储引擎以InnoDB为准。laravel
了解MySQL的特色有助于更好的使用MySQL,MySQL和其它常见数据库最大的不一样在于存在存储引擎这个概念,存储引擎负责存储和读取数据。不一样的存储引擎具备不一样的特色,用户能够根据业务的特色选择适合的存储引擎,甚至是开发一个新的引擎。MySQL的逻辑架构大体以下:面试
MySQL默认的存储引擎是InnoDB,该存储引擎的主要特色是:sql
其它常见存储引擎特色概述:shell
还有不少,再也不一一列举。数据库
选择数据类型的原则:缓存
占用空间小的类型更节省硬件资源,如磁盘、内存和CPU。尽可能使用简单的类型,如能用int
就不用char
,由于后者的排序涉及到字符集的选择,比使用int
复杂。可空列使用更多的存储空间,若是在可空列上建立索引,MySQL须要额外的字节作记录。建立表时,默认都是可空,容易被开发者忽视,最好是手动改成不可空,若是要存储的数据确实不会有空值的话。安全
整型类型包括:bash
它们分别使用八、1六、2四、32和64位存储数字,它们能够表示服务器
范围的数字,前面能够加unsigned修饰,这样可让正数的可表示范围提升1倍,可是没法表示负数。另外,为整型指定长度没什么卵用,数据类型定下来,长度也就相应定下来了。架构
float
和double
就是一般意义上的float
和double
,前者使用32位存储数据,后者使用64位存储数据,和整型同样,为它们指定长度没什么卵用。
decimal
类型比较复杂,支持精确计算,占用的空间也大,decimal
使用每4个字节表示9个数字,如decimal(18,9)
表示数字长度是18,其中小数位9个数字,整数部分9个数字,加上小数点自己,共占用9个字节。考虑到decimal
占用空间较多,以及精度计算很复杂,数据量大的时候能够考虑用bigint
代替之,能够在持久化和读取前对真实数据进行一些缩放操做。
varchar类型数据实际占用空间等于字符串的长度加上1个或2个用来记录字符串长度的字节(当row-format没有被设置为fixed时),varchar很节省空间。当表中某列字符串类型的数据长度差异较大时适合使用varchar。
char的实际占用空间是固定的,当表中字符串数据的长度相差无几或很短时适合使用chart类型。
与varchar和char对应的有varbinary和binary,后者存储的是二进制字符串,和前者相比,后者大小写敏感,不用考虑编码方式,执行比较操做时更快。
须要注意的是:虽然varchar(5)和varchar(200)在存储“hello”这个字符串时使用相同的存储空间,但并不意味着将varchar的长度设置太大不会影响性能,实际上,MySQL的某些内部计算,好比建立内存临时表时(某些查询会致使MySQL自动建立临时表),会分配固定大小的空间存放数据。
blob使用二进制字符串保存大文本,text使用字符保存大文本,InnoDB会使用专门的外部存储区来存放此类数据,数据行内仅存放指向他们的指针,此类数据不宜建立索引(要建立也只能正对字符串前缀建立),不过也不会有人这么干。
若是某列字符串大量重复且内容有限,可以使用枚举代替,MySQL处理枚举时维护了一个“数字-字符串”表,使用枚举能够减小不少存储空间。
datetime存储范围是1001到9999,精确到秒。timestamp存储1970年1月1日午夜以来的秒数,能够表示到2038年。占用4个字节,是datetime占用空间的一半。timestamp表示的时间和时区有关,另外timestamp列还有个特性,执行insert或update语句时,MySQL会自动更新第一个类型为timestamp的列的数据为当前时间。不少表中都有设计有一列叫作UpdateTime,这个列使用timestamp却是挺合适的,会自动更新,前提是系统不会使用到2038年。
尽量使用整型,整型占用空间少,还能够设置为自动增加。尤为别使用GUID,MD5等哈希值字符串做为主键,这类字符串随机性很大,因为InnoDB主键默认是聚簇索引列,因此致使数据存储太分散。另外,InnoDB的二级索引列中默认包含主键列,若是主键太长,也会使得二级索引很占空间。
存储IP最好使用32位无符号整型,MySQL提供了函数inet_aton()和inet_ntoa()进行IP地址的数字表示和字符串表示之间的转换。
InnoDB使用B+树实现索引,举个例子,假设有个People,建表语句以下
CREATE TABLE `people` (
`Id` int(11) NOT NULL AUTO_INCREMENT,
`Name` varchar(5) NOT NULL,
`Age` tinyint(4) NOT NULL,
`Number` char(5) NOT NULL COMMENT '编号',
PRIMARY KEY (`Id`),
KEY `i_name_age_number` (`Name`,`Age`,`Number`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8;
复制代码
插入数据:
它的索引结构大体是这样的:
也就是说,索引列的顺序很重要,若是两行数据的Name列相同,则用Age列比较大小,若是Age列相同,则用Number列比较大小。先用第一列排序,而后是第二列,最后是第三列。
查询的使用应该尽可能从左往右匹配,另外,若是左边列范围查找,右边列没法使用索引;还有就是不能隔列查询,不然后面的索引也没法使用到。如如下几个SQL是正面范例:
Name
=’Abel’ and Age = 2 AND Number = 12312Name
=’Abel’Name
like ‘Abel%’Name
= ‘Andy’ and Age BETWEEN 11 and 20Name
如下几个SQL是反面范例:
若是表中有一列存储较长字符串,假设名字为URL,在此列上建立的索引比较大,有个办法能够缓解:建立URL字符串的数字哈希值的索引。再新建一个字段,好比叫作URL_CRC,专门放置URL的哈希值,而后给这个字段建立索引,查询时这样写:
select * from t where URL_CRC = 387695885 and URL = 'www.baidu.com'
若是数据量比较多,为防止哈希冲突,可自定义哈希函数,或用MD5函数返回值的一部分做为哈希值:
SELECT CONV(RIGHT(MD5('www.baidu.com'),16), 16, 10)
若是字符串列存储的数据较长,建立的索引也很大,这时可使用前缀索引,即:只针对字符串前几个字符作索引,这样能够缩短索引的大小,不过,显然,此类索引在执行order by
和group by
时不起做用。
建立前缀索引时选择前缀长度很重要,在不破坏原来数据分布的状况下尽量选择较短的前缀。举个例子,若是若是大部分字符串是以”abc”开头,那么若是限定前缀索引长度为4,索引值会包含太多的重复的”abcX”。
上面提到的“People”上建立的索引即为多列索引,多列索引每每比多个单列索引更好。
select * from t where f1 = 'v1' and f2 <> 'v2' union all select * from t where f2 = 'v2' and f1 <> 'v1'
多列索引的顺序很重要,一般,不考虑排序和分组查询时,应该把选择性(选择性是指某表索引列不一样数据的个数/总行数。选择性高意味着重复数据少)大的列放到前面。但也有例外,若是能确认某些查询是频繁执行的,则应该优先照顾这些查询的选择性,好比,若是上面的People表中Name的选择性大于Age,查询语句应该这样写:
select * from people where name = 'xxx' and age = xx
Name列放了索引中的左侧比较合适,可是若是某个SQL执行的评率最高,好比
select * from people where name = 'xxx' and age = 20
当age=20的记录在数据库中很是少时,反而把age放到索引列的左端效率更高。把age放了索引左端可能对其它age不等于20的查询来讲不公平,若是不能肯定age=20是最很是频繁的查询条件,仍是要综合考虑,把name放了左侧合适。
聚簇索引是一种数据存储结构,InnoDB在主键的索引的叶子节点中直接保存了数据行,而不是像二级索引那样只是保存了索引列的值和所指向行的主键值。因为这个特性,一个表只能有一个聚簇索引。若是一个表没有定义主键也没有定义具备惟一索引的列,那么InnoDB会生成一个隐藏列,而且在此列设为聚簇索引列。
简单地说,某些查询只须要查询索引列,那么就不用再根据索引B树节点记录的主键ID进行二次查询了。
若是重复在某列建立索引,并不会带来任何好处,只有坏处,应该尽可能避免。好比给主键建立惟一索引和普通索引就是多于的,由于InnoDB的主键默认就是聚簇索引了。
冗余索引和重复索引不一样,好比某个索引是(A,B),另外一个索引是(A),这叫冗余索引,前者能够代替后者,后者不能够代替前者的做用。可是(A,B)和(B)以及(A,B)和(B,A)不算冗余索引,起做用谁也代替不了谁。
若是一个表中已经存在索引(A),如今又想建立索引(A,B),那么只需扩展就的索引就能够,没有必要建立新的索引。须要注意的是若是已经存在索引(A),那么也没有必要在建立索引(A,ID),其中ID指主键,由于索引A默认已经包含了主键了,也算是冗余主键。
可是,有时候,冗余索引也是可取的,假设已经存在索引(A),将其扩展为(A,B)后,由于B列是一个很长的类型,致使用A单独查询时没有之前快了,这时能够考虑新建立索引(A,B)。
不使用的索引徒然增长insert、update和delete的效率,应该及时删除
索引的三星原则:
第一个条原则的意思是where条件中查询的顺序和索引是一致的,就是前面说的从左到右使用索引。
索引不是万能的,当数据量巨大时,维护索引自己也是耗费性能的,应该考虑分区分表存储。
是否向数据库请求了多余的行
好比应用程序只须要10条数据,可是却向数据库请求了全部的数据,在显示在UI上以前抛弃了大部分数据。
是否向数据库请求了多余的列
好比应用程序只须要展示5列,但却经过select * from 把所有的列都查了出来
是否重复屡次执行了相同的查询
应用程序是否能够考虑一次查询而后缓存,后面的用到时可使用第一次查询出来的记录。
MySQL是否在扫描额外的记录
经过查看执行计划能够大概了解须要扫描的记录数,若是这个数字超出了预期,尽量经过添加索引、优化SQL(就是本节的重点),或者改变表结构(如新增一个单独的汇总表,专门供某个语句查询用)来解决。
优化count()
Count有两个做用,一是统计指定的列或表达式,二是统计行数。若是参数传入一列名或者是一个表达式,那么count会统计全部结果不为NULL的行数,若是参数是*,那么count会统计全部行数。这里有一个传表达式的例子:
SELECT count(name like 'B%') from people
关联查询的优化
优化子查询
对于MySQL5.5及如下版本,尽可能用链接代替子查询。
优化group by、distinct
若是可能,尽可能对主键施加这两种操做。
优化limit,好比有SQL
SELECT * from sa_stockinfo ORDER BY StockAcc LIMIT 400, 5
复制代码
MySQL优化器会查找405行全部列数据而后丢弃400。若是能利用覆盖索引查询则没必要查询出这么多列,先修改成:
SELECT * FROM sa_stockinfo i JOIN (SELECT StockInfoID FROM sa_stockinfo ORDER BY StockAcc LIMIT 400,5)t ON i.StockInfoID = t.StockInfoID
复制代码
StockAcc上建有索引,该查询会利用索引覆盖,较快找出符合条件的主键,而后在作联合查询,在数据量大的时候效果明显。
优化union
如无必要,必定要用关键字 union all,这样MySQL把数据放到临时表时不会再作惟一性验证
判断某条记录是否存在,一般的作法是
select count(*) from t where condition
复制代码
最好这样写:
SELECT IFNULL((SELECT 1 from tableName where condition LIMIT 1),0)复制代码
以上内容但愿帮助到你们, 不少PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提高,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货须要的能够免费分享给你们 ,须要戳这里 PHP进阶架构师>>>视频、面试文档免费获取