该文章为知识总结的文章,如果是初学者,建议先从专栏学习:数据库专栏
文章目录
- 一、数据结构的优化
- 二、服务器优化
- 三、线上定位
- 四、查询语句的优化
- 1. sql 语句的时间花在哪儿?
- 2. sql 语句的执行时间,又花在哪儿了?
- 3. sql 语句的优化思路?
- 4. 如何定量分析查的多少行,和是否沿着索引查?
- 5. 常见的查询优化
- 6. 缓存优化
- 7. 读写分离
- 8. mysql 的分库分表
- 9. EXPLAIN使用
- 五、索引及优化
- 1. 索引的作用
- 2. 索引的分类
- 3. 索引的优点(查的快)
- 4. 索引的缺点(增删改慢)
- 5. 哪些情况或字段适合加索引
- 6. 哪些情况不适合创建索引
- 7. 哪些情况会造成索引失效
- 8. 索引原理
- 六、插入优化
- 七、大表优化
- 1. 限定数据的范围
- 2. 读/写分离
- 3. 垂直分区
- 4. 水平分区
- 5. 数据库分库策略
- 6. 分库分表之后,id 主键如何处理?
一、数据结构的优化
三大范式:
- 数据库设计尽量遵循三范式
- 根据实际情况进行取舍,有时可能会拿冗余换速度,减少表的关联查询,用空间换时间
- 对于需要经常联合查询的表,可以建立中间表以提高查询效率
- 对于字段较多的表,如果有些字段的使用频率很低,可以将这些字段分离出来形成新表
数据库命令规范:
- 所有数据库对象名称必须使用小写字母并用下划线分割
- 数据库对象的命名要能做到见名识意,并且最后不要超过 32 个字符
- 所有存储相同数据的列名和列类型必须一致(一般作为关联列,如果查询时关联列类型不一致会自动进行数据类型隐式转换,会造成列上的索引失效,导致查询效率降低)
列的选择:
- 字段类型优先级 整型 > date,time > char,varchar > blob
- 尽量不要有null,可以设置为不允许空,或者加一个默认值
- 数据大小够用就行
- 不要使用大数据字段,如TEXT,BLOB
- 财务数据使用decimal,不会丢失精度
主键的选择:
- 在 myisam 中,字符串索引会被压缩,用字符串做主键性能不如整型
- 用递增的值,不要用离散的值,离散值会导致文件在磁盘的位置有间隔,浪费空间且不易连续读取
二、服务器优化
优化服务器硬件
- 配置较大的内存
- 配置高速磁盘,比如 SSD
- 配置多核处理器, MySQL 是多线程的数据库,多处理器可以提高同时执行多个线程的能力
优化 MySQL 的参数
-
修改my.conf 或者 my.ini 配置文件
-
key_buffer_size:表示索引缓冲区大小,缓存数据,只对MyISAM表起作用,不是越大越好,大小取决于内存
-
innodb_buffer_pool_size:表示InnoDB类型表和索引的最大缓存,值越大查询速度越快
-
query_cache_size:表示查询缓冲区大小,MySQL8以上已经移除
-
max_connections:表示数据库的最大连接数,也不是越多越好,过多连接可能会导致僵死
三、线上定位
发现系统运行缓慢,如何定位和分析查询慢的 sql 语句 ?
- 开启 mysql 慢日志查询 定位查询较慢的 sql 语句 (200ms 500ms )
- 使用 EXPLAIN 关键字可以让你知道 MySQL 是如何处理你的 SQL 语句的。这可以帮你分析你的查询语句或是表结构的性能瓶颈。 EXPLAIN 的查询结果还会告诉你你的索引主键被如何利用的,你的数据表是如何被搜索和排序的……等等,等等
- 代码中可以使用 AOP 的操作 对每个持久层的 service 方法 打印执行时间,将所有执行时间较长的 sql 语句进行预警
- 查看sql语句是否出现了临时表,临时表可能会通过IO落地到磁盘上,要尽可能避免
四、查询语句的优化
1. sql 语句的时间花在哪儿?
- 等待数据的等待时间,以及执行SQL语句时间.
- 这两个时间并非孤立的, 如果单条语句执行的快了,对其他语句的锁定的也就少了
2. sql 语句的执行时间,又花在哪儿了?
- 查 ----> 沿着索引查,甚至全表扫描
- 取 ----> 查到行后,把数据取出来(sending data)
3. sql 语句的优化思路?
- 不查, 通过业务逻辑来计算,比如论坛的注册会员数,我们可以根据前 3 个月统计的每天注册数, 用程序来估算
- 少查, 尽量精准数据,少取行. 我们观察新闻网站,评论内容等,一般一次性取列表 10-30 条左右
- 必须要查,尽量走在索引上查询行
- 取时, 取尽量少的列
4. 如何定量分析查的多少行,和是否沿着索引查?
5. 常见的查询优化
- SELECT 语句务必指明字段名称(避免直接使用 select * ),使用*时,分析器,会要分析出都有什么列,会消耗很多时间,同时执行的时候,会返回很多没有必要的列
- SQL 语句要避免造成索引失效的写法
- 当只需要一条数据的时候,使用 limit 1,这样就通过sql语句过滤,而避免了执行时的过滤
- 如果排序字段没有用到索引,就尽量少排序
- 如果限制条件中其他字段没有索引,尽量少用 or
- 避免在 where 子句中对字段进行 null 值判断
- 不建议使用%前缀模糊查询
- 避免在 where 子句中对字段进行表达式操作
- Join 优化 能用 inner join 就不用 left join right join,如必须使用 一定要已小表为驱动
- 可以使用连接查询(JOIN)代替子查询,连接查询时不需要建立临时表,其速度比子查询快
6. 缓存优化
- 为了提高查询速度,我们可以通过不同的方式去缓存我们的结果从而提高响应效,可以去打开查询缓存或者使用Redis
7. 读写分离
- 如果数据库的使用场景读的操作比较的时候,为了避免写的操作所造成的性能影响 可以采用读写分离的架构,读写分离,解决的是,数据库的写入,影响了查询的效率。读写分离的基本原理是让主数据库处理事务性增、改、删操作(INSERT、 UPDATE、 DELETE),而从数据库处理 SELECT 查询操作
8. mysql 的分库分表
9. EXPLAIN使用
select_type,表示 SELECT 语句的类型,取值如下
- SIMPLE:表示简单查询,其中不包含连接查询和子查询
- PRIMARY:主键查血
- UNION:表示连接查询
- UNION RESULT :连接查询的结果
- SUBQUERY:子查询
type ,表示表的连接类型 ,取值如下:
- system:表仅有一行,这是 const 类型的特列,平时不会出现,这个也可以忽略不计
- const:数据表最多只有一个匹配行,因为只匹配一行数据,所以很快,常用于 PRIMARY KEY 或者 UNIQUE 索引的查询,
可理解为 const 是最优化的
- eq_ref :读取本表中和关联表表中的每行组合成的一行,索引是主键或唯一非 NULL 索引
- ref:查询条件索引既不是 UNIQUE 也不是 PRIMARY KEY 的情况
- ref_or_null:该联接类型如同 ref,但是添加了 MySQL 可以专门搜索包含 NULL 值的行。在解决子查询中经常使用该联接类型的优化。
上面这五种情况都是很理想的索引使用情况。
- index_merge:该联接类型表示使用了索引合并优化方法。在这种情况下, key 列包含了使用的索引的清单
- unique_subquery:是一个索引查找函数,可以完全替换子查询,效率更高。
- index_subquery:只检索给定范围的行,使用一个索引来选择行
- range:只检索给定范围的行,使用一个索引来选择行
- index:该联接类型与 ALL 相同,除了只有索引树被扫描。
- ALL:对于每个来自于先前的表的行组合,进行完整的表扫描,性能最差
**possible_keys,可能用到的索引 **
**key,最终用的索引 ,如果没有选择索引,键是 NULL **
五、索引及优化
注意避免冗余索引,比如说(name)和(name,price),(name)就是冗余索引
1. 索引的作用
当表中的数据量越来越大时,索引对于性能的影响愈发重要。索引优化应该是对查询性能优化最有效的手段了。
索引能够轻易将查询性能提高好几个数量级。有了索引相当于我们给数据库的数据加了目录一样,可以快速的找到数据,如果不适用索引则需要一点一点去查找数据。 简单来说提高数据查询的效率。
2. 索引的分类
- 普通索引 index :加速查找
- 唯一索引
- 主键索引: primary key :加速查找+约束(不为空且唯一)
- 唯一索引: unique:加速查找+约束 (唯一)
- 联合索引(组合索引)
3. 索引的优点(查的快)
- 可以通过建立唯一索引或者主键索引,保证数据库表中每一行数据的唯一性.
- 建立索引可以大大提高检索的数据,以及减少表的检索行数
- 在表连接的连接条件 可以加速表与表直接的相连
- 在分组和排序字句进行数据检索,可以减少查询时间中 分组 和 排序时所消耗的时间(数据库的记录会重新排序)
- 建立索引,在查询中使用索引 可以提高性能
4. 索引的缺点(增删改慢)
- 在创建索引和维护索引 会耗费时间,随着数据量的增加而增加
- 索引文件会占用物理空间,除了数据表需要占用物理空间之外,每一个索引还会占用一定的物理空间
- 当对表的数据进行 INSERT,UPDATE,DELETE 的时候,索引也要动态的维护,这样就会降低数据的维护速度,(建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会膨胀很快)
5. 哪些情况或字段适合加索引
- 在经常需要搜索的列上,可以加快索引的速度
- 主键列上可以确保列的唯一性
- 在表与表的而连接条件上加上索引,可以加快连接查询的速度
- 在经常需要排序(order by),分组(group by)和的 distinct 列上加索引 可以加快排序查询的时间
6. 哪些情况不适合创建索引
- 查询中很少使用到的列 不应该创建索引,如果建立了索引然而还会降低 mysql 的性能和增大了空间需求.
- 很少数据的列也不应该建立索引,比如 一个性别字段 0或者1,在查询中,结果集的数据占了表中数据行的比例比较大,mysql 需要扫描的行数很多,增加索引,并不能提高效率
- 定义为 text 和 image 和 bit 数据类型的列不应该增加索引,
- 当表的修改(UPDATE,INSERT,DELETE)操作远远大于检索(SELECT)操作时不应该创建索引,这两个操作是互斥的关系
7. 哪些情况会造成索引失效
- 如果条件中有 or,即使其中有条件带索引也不会使用(这也是为什么尽量少用 or 的原因)
- 索引字段的值不能有 null 值,有 null 值会使该列索引失效
- 对于多列索引,不是使用的第一部分,则不会使用索引(最左原则)
- like 查询以%开头
- 如果列类型是字符串,那一定要在条件中将数据使用单引号引用起来,否则不使用索引
- 在索引的列上使用表达式或者函数会使索引失效
8. 索引原理
所以说,如果我们写 select * from user where indexname = 'xxx’这样没有进行任何优化的 sql 语句,默认会这样做:
- 定位到记录所在的页:需要遍历双向链表,找到所在的页
- 从所在的页内中查找相应的记录:由于不是根据主键查询,只能遍历所在页的单链表了
很明显,在数据量很大的情况下这样查找会很慢!这样的时间复杂度为 O(n)
使用索引之后
- 其实就是将无序的数据变成有序(相对):
- 很明显的是:没有用索引我们是需要遍历双向链表来定位对应的页,现在通过 “目录” 就可以很快地定位到对应的页上了!(二分查找,时间复杂度近似为 O(logn))
- 其实底层结构就是 B+树, B+树作为树的一种实现,能够让我们很快地查找出对应的记录
面试题
有商品表, 有主键,goods_id, 栏目列 cat_id, 价格 price说:在价格列上已经加了索引,但按价格查询还是很慢,
问可能是什么原因,怎么解决?
答: 在实际场景中,一个电商网站的商品分类很多,直接在所有商品中,按价格查商品,是极少的,一般客户都来到分类下,然后再查.
改正: 去掉单独的 Price 列的索引, 加 (cat_id,price)复合索引再查询
索引优化策略
- 选择唯一性索引
- 唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。
- 为经常需要排序、分组和联合操作的字段建立索引:
- 为常作为查询条件的字段建立索引。
- 限制索引的数目: 越多的索引,会使更新表变得很浪费时间。
- 尽量使用数据量少的索引
- 如果索引的值很长,那么查询的速度会受到影响。
- 尽量使用前缀来索引
- 如果索引字段的值很长,最好使用值的前缀来索引。
- 删除不再使用或者很少使用的索引
- 最左前缀匹配原则,非常重要的原则。
- 尽量选择区分度高的列作为索引
- 区分度的公式是表示字段不重复的比例
- 索引列不能参与计算,保持列“干净”:带函数的查询不参与索引。
- 尽量的扩展索引,不要新建索引。
六、插入优化
InnoDB:
-
禁用唯一性检查,唯一性校验会降低插入记录的速度,可以在插入记录之前禁用唯一性检查,插入数据完成后再开启
-
禁用外键检查,插入数据之前执行禁止事务的自动提交
-
禁止自动提交,插入数据之前执行禁止事务的自动提交
-
批量插入数据,可以使用一条 INSERT 语句插入一条数据
七、大表优化
当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下:
1. 限定数据的范围
务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内;
2. 读/写分离
经典的数据库拆分方案,主库负责写,从库负责读;
3. 垂直分区
根据数据库里面数据表的相关性进行拆分。 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。
简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。 如下图所示,这样来说大家应该就更容易理解了
4. 水平分区
保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。
水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。
分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 水平拆分最好分库 。
5. 数据库分库策略
- 客户端代理: 分片逻辑在应用端,封装在jar包中,通过修改或者封装JDBC层来实现。 当当网的 Sharding-JDBC 、阿里的TDDL是两种比较常用的实现。
- 中间件代理: 在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。 我们现在谈的 Mycat 、360的Atlas、网易的DDB等等都是这种架构的实现。
6. 分库分表之后,id 主键如何处理?
因为要是分成多个表之后,每个表都是从 1 开始累加,这样是不对的,我们需要一个全局唯一的 id 来支持。
生成全局 id 有下面这几种方式:
- UUID:不适合作为主键,因为太长了,并且无序不可读,查询效率低。比较适合用于生成唯一的名字的标示比如文件的名字。
- 数据库自增 id : : 两台数据库分别设置不同步长,生成不重复ID的策略来实现高可用。这种方式生成的 id 有序,但是需要独立部署数据库实例,成本高,还会有性能瓶颈。
- 利用 redis 生成 id : 性能比较好,灵活方便,不依赖于数据库。但是,引入了新的组件造成系统更加复杂,可用性降低,编码更加复杂,增加了系统成本。
- Twitter的snowflake算法 、美团的Leaf分布式ID生成系统