MySQL相关

674 阅读27分钟

MySQL

SQL

  1. distinct 去重
  2. 起别名:AS 或者 空格
  3. 模糊查询
    • like:%匹配任意字符(含0个)_匹配单个字符
    • between and:闭区间
    • in (value1, value2, ...):判断是否位于列表
    • is null | is not null
  4. 排序查询:order by field1 field2 asc|desc可以每一列都按照不同的升序/降序进行排列,默认为升序。除了字段之外,还可以使用表达式、函数、别名进行排序。order by 一般放在最后,除了 limit 之外。
  5. 分组查询:group by expression
    • select 分组函数,fields from tablename [where] group by 分组列表[having][order by]
    • select 后接的必须是分组函数、group by 后出现的字段
    • 分组前筛选,数据来源于原始表,使用 where 关键字,并且位于 group by 的前边
    • 分组后筛选,数据为分组后的结果集,使用 having 语句,并且位于 group by 的后边
  6. 连接查询
    • select 查询列表 from t1 [连接类型] join t2 on 连接条件
    • 内连接: inner join
      • 等值连接
      • 非等值连接
      • 自连接:相当于等值连接,连接的表均为同一张表
    • 外连接:
      • 用于查询一个表中有,另一个表中没有的记录
      • 外连接查询结果为主表中的所有记录。如果从表中有与之匹配的,则显示匹配值;没有匹配的,显示null。也就是说,外连接的查询结果是内连接结果加上主表中有从表中没有的记录。
      • 左外连接:left [outer] join,左边的为主表
      • 右外连接: right [outer] join,右边为主表
      • 全外连接: full [outer] join
    • 交叉连接:
      • cross join
      • 笛卡尔乘积
  7. 子查询:出现在其他语句中的 select语句,称为子查询或内查询。
    • 标量子查询:返回结果只有一行一列
    • 多行子查询:返回多行
      • IN/NOT IN:等于/不等于列表中的任意一个(可以替代 EXISTS)
      • ANY/SOME:和子查询返回的某一个值比较
      • ALL:和子查询返回的所有值比较
  8. 分页查询
    • limit [offset] size,offset 表示起始索引,从0开始,默认为0,size 表示个数
  9. 联合查询
    • union 将多条查询语句的结果合并成一个结果
    • 要求各个查询语句的列数是一致的
    • 各个查询语句每一列的类型和顺序最好一致
    • union 关键字默认去重,使用 union all 包含所有结果
  10. 插入语句:
    • insert into table (field1,...) values(value1,...)支持插入多行
    • insert into table set field1=value1 , field2=value2,...只能插入一行
  11. 修改语句
    • update table set filed=value,... where...
  12. 删除语句
    • delete from table where...删除一或多行
    • truncate table tablename
    • delete 删除后,插入数据,自增列从断点开始;truncate 从1开始,且在事务中支持回滚
    • truncate 删除没有返回值,delete 有返回值,在事务中不支持回滚

总体来说,查询语法如下:

SELECT DISTINCT select_list
FROM left_table join_type
JOIN right_table 
ON join_condition
WHERE where_condition
GROUP BY group_by_list
HAVING having_condition
ORDER BY order_by_condition
LIMIT offset size

约束

  1. NOT NULL:非空
  2. DEFAULT:设置默认值
  3. PRIMARY KEY:设置主键
  4. UNIQUE: 唯一,可以为空
  5. FOREIGN KEY:外键,用于保证该字段的值必须来自主表的关联列的值。在从表中添加外键约束,用于引用主表中某列的值

外键:

  • 要求在从表设置外键关系
  • 从表的外键列的类型和主表的关联列的类型要求一致或兼容,名称无要求
  • 主表的关联列必须是一个 key (主键或者唯一)
  • 插入数据,先插入主表,后插入从表
  • 删除数据,先删除从表,再删除主表

事务

事务(Transaction)是并发控制的基本单位。所谓的事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。

ACID

  • 原子性(Atomicity):事务是不可分割的最小单元,所有操作要么全部提交成功,要么全部回滚失败。
  • 一致性(Consistency):数据库在事务执行前后保持一致性。
  • 隔离性(Isolation):事务所做的修改在最终提交前,对其他事务不可见。
  • 持久性:事务一旦提交,所做修改会永远保持到数据库,系统崩溃也不能丢失。

其中,一致性保证了事务的执行结果是正确的。在无并发情况下,原子性保证了一致性。在并发情况下,原子性和隔离性保证了一致性。持久性用来应对数据库崩溃。

并发一致性问题

  1. 丢失修改: 两个事务都对一个数据进行修改,后者覆盖了前者的修改。
  2. 读脏数据: T1 修改数据,T2读取了该数据后,T1又撤销修改,T2读到的是脏数据。
  3. 不可重复读: T2读取数据,T1修改了该数据后,T2再次读取,两次结果不同。
  4. 幻读:T1读取某个范围的数据,T2在范围中插入新数据,T1再次读取该范围的数据,两次读取的结果不同。

隔离级别

  • 未提交读(read uncommitted):事务中的修改,即使没有提交,对其他事务也是可见的。
  • 提交读(read committed):一个事务只能读取已经提交的事务所做的修改。也就是事务修改提交前其他事务不可见。可以解决脏读问题。
  • 可重复读(repeatable read):保证同一事务中多次读取同样数据的结果一样。可以解决脏读和不可重复读问题。
  • 可串行化(serializable):强制事务串行执行。

隐式事务与显式事务

隐式事务:mysql 默认开启了 autocommit 选项,会隐式提交事务。

显式事务:

  1. 开启事务:set autocommit = 0; start transaction;该语句可选。
  2. 事务中的sql语句(select, insert, update, delete)
  3. 结束事务:commit;提交 rollback;回滚

范式

第一范式(1NF):数据库表中的字段都是单一属性的,不可再分。这个单一属性由基本类型构成,包括整型、实数、字符型、逻辑型、日期型等。

第二范式(2NF):数据库表中不存在非关键字段对任一候选关键字段的部分函数依赖(部分函数依赖指的是存在组合关键字中的某些字段决定非关键字段的情况),也即所有非关键字段都完全依赖于任意一组候选关键字。

第三范式(3NF):在第二范式的基础上,数据表中如果不存在非关键字段对任一候选关键字段的传递函数依赖则符合第三范式。所谓传递函数依赖,指的是如 果存在"A → B → C"的决定关系,则C传递函数依赖于A。因此,满足第三范式的数据库表应该不存在如下依赖关系: 关键字段 → 非关键字段 x → 非关键字段y

视图

视图是一种虚拟的表,可以和普通表一样使用,行和列的数据来自定义视图的查询中使用的表,在使用视图的时候动态生成,只保存 sql 逻辑,不保存查询结果。可以应用于多个地方用到同样的查询结果的情景。

优点:

  • 可以重用 sql 语句
  • 简化复杂的 sql 操作
  • 保护数据,提高安全性

创建视图: create view 视图名 as 查询语句

修改视图: create or replace view 视图名 as 查询语句或者 alter view 视图名 as 查询语句

删除视图:drop view 视图名

查看视图:desc 视图名show create view 视图名

mysql 架构介绍

  1. 连接层:这一层是客户端和连接服务,包含本地 socket 通信和大多数基于 C/S 工具实现的类似 tcp/ip 通信。用于完成连接处理、授权认证和安全方案。在该层上引入了线程池的概念。
  2. 服务层:完成核心服务功能。SQL接口、缓存查询、SQL 分析和优化以及部分内置函数的执行。在该层,服务器会解析查询并创建响应的内部解析树,并对其完成响应的优化如确定查询表的顺序,是否利用索引等,最好生成执行操作。如果是 select 语句,还会查询内部缓存。
  3. 引擎层:负责数据的存储和提取,服务器通过 API 与存储引擎通信。

索引

索引是一种特殊的文件,包含对数据表里所有记录的引用指针。

MySQL 官方定义:索引是帮助 MySQL 高效获取数据的数据结构。在数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这种数据结构就是索引。索引可以理解为已排序的快速查找数据结构。平时所说的索引,如果没有特别指明,都是指的是 B+ 树。

一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上。

优点:

  • 提高数据检索的效率,降低数据库的 IO 成本
  • 通过索引对数据进行排序,降低数据排序的成本,降低了 CPU 的消耗
  • 创建唯一性索引,保证数据库表中每一行数据的唯一性。
  • 加速表与表之间的连接

缺点:

  • 占用空间
  • 降低了更新表的数据,调整数据的同时,还会调整索引信息

Mysql存储基本知识

Mysql基本存储结构是页,各个数据页组成双向链表,每个数据页中的记录又可以组成一个单向链表。查找时,先遍历双向链表,定位到所在的页。每个数据页都会为所存储的记录生成页目录,通过主键查找会在页目录使用二分法定位到对应的槽,从槽中遍历找到指定记录。非主键搜索,会依次遍历单链表中的每条记录。

SQL 执行顺序:

SELECT DISTINCT select_list
FROM left_table join_type
JOIN right_table 
ON join_condition
WHERE where_condition
GROUP BY group_by_list
HAVING having_condition
ORDER BY order_by_condition
LIMIT offset size

mysql 处理的顺序:

FROM left_table
ON join_condition
join_type JOIN right_table
WHERE where_condition
GROUP BY group_by_list
HAVING having_condition
SELECT
DISTINCT select_list
ORDER BY order_by_condition
LIMIT offset size

创建索引

  1. 普通索引:一个索引只包含一个列
  2. 唯一索引:索引列的值必须唯一,但允许有空值
  3. 复合索引:一个索引包含多个列
  4. 全文索引:对大文本进行索引。必须是MyISAM引擎的数据表
  • 创建: CREATE [UNIQUE] INDEX indexName ON table(columnlist); 或者 ALTER table ADD [UNIQUE] INDEX [indexname] ON (columnlist)
  • 删除DROP INDEX [indexname] on table
  • 查看SHOW INDEX FROM table

B+树索引

B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具有 B Tree 的平衡性,并且通过顺序访问指针来提高区间查询的性能。

当没有索引时,我们需要遍历双向链表来定位所在的页,现在通过索引,可以很快定位到所在的页上。底层采用B+树实现。B+树是平衡树的一种,如果对这棵树进行增删改,需要重新维持平衡,有额外的开销。

hash索引

采用hash算法,把键值换成hash值,只需要一次hash算法就可以立即定位,速度快。

局限:

  • 无法利用索引进行排序
  • 不支持最左匹配
  • 哈希碰撞问题,大量重复键值效率低
  • 不支持范围查询

非聚集索引和聚集索引

  • 聚集索引是以主键创建的,非聚集索引是以非主键创建的。
  • 聚集索引中键值的逻辑顺序决定了表中相应行的物理顺序,非聚集索引则是数据存储在一个地方,索引存储在另一个地方,索引带有指针指向数据的存储位置。
  • 聚集索引在叶子节点存储表中的数据,非聚集索引在叶子节点存储的主键和索引列值
  • 使用非聚集索引查询出数据,在到叶子上的主键查到想要查找的数据,也称为二级索引。
  • 非聚集索引可以是多列的。
  • 覆盖索引:要查询出的列和索引是对应的,不用再查聚集索引
  • 使用场景选择

两种引擎索引的比较

MyISAM: B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候,首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。

InnoDB: 其数据文件本身就是索引文件。相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的一个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。而其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值而不是地址,这也是和MyISAM不同的地方。在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再走一遍主索引。 因此,在设计表的时候,不建议使用过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。

创建索引考虑因素

  1. 主键自动创建唯一索引
  2. 频繁作为查询条件的字段
  3. 查询中与其他表关联的字段,外键关系建立索引
  4. 查询中排序的字段通过索引访问会提高排序速度
  5. 查询中统计或者分组字段
  6. 高并发下倾向于创建组合索引而不是单键索引
  7. where 条件里用不到的字段不创建索引
  8. 频繁更新的字段不适合做索引
  9. 表记录少时不需要建立索引
  10. 经常增删改的表不要建立索引
  11. 数据重复且分布均匀的表字段建立索引没有太大实际效果

索引使用注意点

  1. 索引应建立在那些将用于JOIN,WHERE判断和ORDER BY排序的字段上
  2. 最左匹配原则:联合索引只能用于查找key是否存在,遇到范围查询就不能进一步匹配了,后序退化为线性查找。如有索引(a, b, c, d),查询条件a = 1 and b = 2 and c > 3 and d = 4,则会在每个节点依次命中a、b、c,无法命中d。(很简单:索引命中只能是相等的情况,不能是范围匹配) 。
  3. 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
  4. 应尽量避免在 where 子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描
  5. 应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描
  6. in 和 not in 也要慎用,否则会导致全表扫描。对于连续的数值,能用 between 就不要用 in 了
  7. 模糊查询:like keyword% 索引有效,其他无效
  8. 尽量避免在where子句中对字段进行函数操作
  9. 不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引
  10. 复合索引应尽可能的让字段顺序与索引顺序相一致,且必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引

索引失效

左连接 left join 中,左表全都有,left join 用于确定如何从右表搜索行,则右表建立索引,效率更好。右连接同理。

  1. 全值匹配
  2. 最佳左前缀
  3. 不在索引列上做任何操作:计算、函数、类型转换
  4. 存储引擎不能使用索引中范围条件右边的列
  5. 尽量使用覆盖索引,减少 select *
  6. 在使用不等于的时候无法使用索引,导致全表扫描
  7. is null, is not null 不能使用索引
  8. like %xxxlike %xxx%无法使用索引,只有通配符卸载最右才能使用索引。当使用like %xx%时,可以使用覆盖索引,会使用到索引,但是类型为 range
  9. 字符串不加单引号,因为会做隐式类型转换
  10. 少用 or,用它连接时索引失效

建议:

  1. 单值索引,尽量选择针对当前查询过滤性更好的索引
  2. 选择组合索引,当前查询中过滤性最好的字段在索引字段顺序中,位置越靠前越好
  3. 选择组合索引时,尽量选择考研能够包含当前 query 中的 where 子句中更多字段的索引
  4. 尽可能通过分析和调整查询语句的写法来达到选择合适索引的目的。

索引性能分析:Explain

Explain 关键字用来模拟优化器执行 SQL 查询语句,从而知道 MySQL 是如何处理 SQL 语句的,从而分析查询语句或者表结构的性能瓶颈。

使用 explain + sql 语句可以查看对应的 SQL 语句的执行计划。

  1. id
    • 是 select 查询的序列号,包含一组数字,表示查询中执行 select 子句或者操作表的顺序
    • 当 id 相同时,执行顺序由上到下
    • 如果有子查询,id 的序号会递增, id 值越大优先级越高,越先被执行
  2. select_type
    • 代表查询的类型,用于区别普通查询、联合查询、子查询等复杂查询
    • simple: 表示是简单的查询,不包含子查询或者 union
    • primary: 查询中如果包含任何复杂的子部分,最外层查询被标记为 primary
    • subquery: 表示 select 或者 where子查询
    • derived: 在 from 中的子查询,mysql 会递归执行这些子查询,把结果放在临时表
    • union: 若第二个 select 出现在 union 之后,被标记为 union,若 union 包含在 from 的子查询中,外层被标记为 derived
    • union result: 从 union 表获取结果的 select
  3. table: 显示的该行数据关于的表名
  4. type
    • 显示查询使用了何种类型
    • system: 表只有一行记录
    • const: 表示通过索引一次就找到了,用于比较主键或者唯一索引。因为只匹配一行数据,所以很快。如果将主键放在 where 列表,mysql 就能将该查询转换为一个常量。
    • eq_ref: 唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见于主键或者唯一索引扫描
    • ref: 非唯一性索引扫描,返回匹配某个单独值得所有行,本质上也是一种索引访问,但是可能会找到多个符合条件的行
    • range: 只检索给定范围的行,使用一个索引来选择行。一般用于范围查询(<,>,between, in),比全表扫描要更快,不用扫描全部索引。
    • index: 全表扫描,但是遍历的是索引树,而非数据文件
    • ALL:全表扫描,遍历数据文件
  5. possible_keys:
    • 显示可能应用在该表的索引,查询涉及到的字段上如果存在索引,则该索引被列出,但不一定被查询实际使用
  6. key:
    • 实际使用的索引,如果为 NULL,则没有使用索引
    • 查询中如果使用了覆盖索引,则该索引仅出现在 key 列表
  7. key_len
    • 表示索引中使用的字节数,可以通过该列计算查询中使用的索引的长度,不损失精确性的情况,长度越短越好
    • 显示的值是索引字段的最大可能长度,并非实际使用长度
  8. ref
    • 显示索引的哪一列被使用了
  9. rows
    • 根据表统计信息及索引选用情况,大致估算出找到所需记录所需读取的行数
  10. extra
    • using filesort: 说明 mysql 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取
    • using temporary: 使用了临时表保存中间结果,在对结果排序时使用临时表,常见于 order by 和 group by
    • using index: 表示相应的 select 操作中使用了覆盖索引,避免访问了表的数据行。
      • 如果同时出现了 using where 表示索引被用来执行索引键值的查找;否则表示用来读取数据而非执行查找动作
      • 覆盖索引,也称索引覆盖,就是指 select 的数据列只用从索引中就能取得,不用读取数据行,也就是查询列被所建立的索引覆盖

锁机制

锁分类

  1. 按照锁的粒度分为表级锁和行级锁。表级锁粒度大,资源消耗小,加锁快,不会死锁,但是并发度低。行级锁会减少数据库操作的冲突,并发度高,加锁开销大,会死锁。
  2. 按照是否可写分为共享锁(读锁S)和排他锁(写锁X)

死锁及避免

InnoDB的行级锁是基于索引实现的,如果查询语句未命中任何索引,那么InnoDB会使用表级锁。不同于MyISAM总是一次性获得所需的全部锁,InnoDB的锁是逐步获得的,当两个事务都需要获得对方持有的锁,导致双方都在等待,这就产生了死锁。 我们可以采取以下方式避免死锁:

  • 通过表级锁来减少死锁产生的概率;
  • 多个程序尽量约定以相同的顺序访问表(这也是解决并发理论中哲学家就餐问题的一种思路);
  • 同一个事务尽可能做到一次锁定所需要的所有资源。

两个引擎使用的锁

MyISAM采用表级锁(table-level locking)。 InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁。

查询缓存

开启查询缓存后在同样的查询条件以及数据情况下,会直接在缓存中返回结果。这里的查询条件包括查询本身、当前要查询的数据库、客户端协议版本号等一些可能影响结果的信息。因此任何两个查询在任何字符上的不同都会导致缓存不命中。

缓存建立之后,Mysql的查询缓存系统会跟踪查询中涉及的每张表,如果这些表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效。

缓存虽然能够提升数据库的查询性能,但是缓存同时也带来了额外的开销,每次查询后都要做一次缓存操作,失效后还要销毁。 因此,开启缓存查询要谨慎,尤其对于写密集的应用来说更是如此。如果开启,要注意合理控制缓存空间大小。

存储引擎比较

InnoDB

InnoDB的最大特色就是支持了ACID兼容的事务(Transaction)功能。

是 MySQL 默认的事务型存储引擎,只有在需要它不支持的特性时,才考虑使用其它存储引擎。

实现了四个标准的隔离级别,默认级别是可重复读(REPEATABLE READ)。在可重复读隔离级别下,通过多版本并发控制(MVCC)+ 间隙锁(Next-Key Locking)防止幻影读。

特点:

  • 支持行锁,采用MVCC来支持高并发,有可能死锁
  • 支持事务
  • 支持外键
  • 支持崩溃后的安全恢复
  • 不支持全文索引

MyISAM

现在大多数时候我们使用的都是InnoDB存储引擎,但是在某些情况下使用MyISAM更好,比如:MyISAM更适合读密集的表,而InnoDB更适合写密集的的表。 在数据库做主从分离的情况下,经常选择MyISAM作为主库的存储引擎。

  • 不支持行锁(MyISAM只有表锁),读取时对需要读到的所有表加锁,写入时则对表加排他锁;
  • 不支持事务
  • 不支持外键
  • 不支持崩溃后的安全恢复
  • 在表有读取查询的同时,支持往表中插入新纪录
  • 支持BLOB和TEXT的前500个字符索引,支持全文索引
  • 支持延迟更新索引,极大地提升了写入性能
  • 对于不会进行修改的表,支持 压缩表 ,极大地减少了磁盘空间的占用

比较

使用show engines查询引擎。

对比项 MyISAM InnoDB
主键和外键 不支持 支持
事务 不支持 支持
表锁 行锁,适合高并发操作
缓存 只缓存索引,不缓存真实数据 缓存索引以及真实数据,对内存要求较高
表空间

慢查询分析

查询优化

小表驱动大表

小的数据集驱动大的数据集

select * from A where id in(select id from B)
等价于:
for select id from B
    for select * from A where A.id = B.id

当 B 表的数据集必须小于 A 表的数据集时,用 in 优于 exists。

select * from A where exists (select * from B where B.id = A.id)
等价于:
for select * from A
    for select * from B where B.id=A.id

当 A 表的数据集小于 B 表的数据集时,用 exists 优于 in。

select ... from table where exists (subquery)

该语法的理解为:将主查询的数据,放到子查询做条件验证,根据验证结果(TRUE 或 FALSE)来决定主查询的数据结果是否得以保留。

  • EXISTS(subquery) 只返回 TRUE 或 FALSE,因此子查询中的 SELECT * 也可以是 SELECT 1 或其他,因为在实际执行时会忽略 SELECT 清单,因此没有区别
  • EXISTS 子查询的实际执行过程可能经过了优化而不是理解上的逐条对比
  • EXISTS 子查询往往也可以用条件表达式、其他子查询或者 JOIN 来替代

order by 优化

MySQL 支持两种方式的排序, FileSort 和 Index。其中 Index 效率更高,使用 MySQL 扫描索引本身完成排序。

ORDER BY 满足两种情况时,会使用 Index 方式进行排序。 :

  • ORDER BY 语句使用索引最左前列
  • 使用 WHERE 子句和 ORDER BY 子句条件列组合满足索引最左前列

因此,要尽可能在索引列上完成排序操作,遵照索引的最佳左前缀原则。

如果 ORDER BY 不在索引列上, filesort 有两种算法:MySQL 启动双路排序和单路排序。

  • 双路排序:两次扫描磁盘,读取行指针和 order by 列,进行排序,然后扫描已经排序的列表,按照列表中的值重新从列表中读取对应的数据输出。
  • 单路排序:MySQL 4.1 之后使用。从磁盘读取查询需要的所有列,按照 order by 列在 buffer 对它们进行排序,然后扫描排序后的列表进行输出。会使用更多的空间,因为把每一行都保存在内存之中了。

由于单路排序要取出所有数据,可能导致每次只能取 buffer 大小的数据,从而需要多次 IO 操作。

提高 order by 的速度:

  1. 不要使用 select * 。因为使用 select * 会取出所有的列数据,更容易把 buffer 占满,从而导致多次 I/O.
  2. 尝试提高 sort_buffer_size
  3. 尝试提高 max_length_for_sort_data

group by 优化

group by 实质是先排序后分组,遵照索引建立的最佳左前缀。

当无法使用索引列时,尝试增大sort_buffer_sizemax_length_for_sort_data

where 高于 having,能写在 where 限定的条件就不要去 having 限定了。

分布式相关

切分

水平切分

又称为 Sharding,将同一个表中的记录拆分到多个结构相同的表中,将数据分布到集群的不同节点上,缓解单个数据库的压力。

一般水平查分根据表中的某一字段(一般是主键)取模,将一张表的数据拆分到多个表。采用 hash(key)%N 的方法。使用单独一个数据库来存储映射关系。

分片的选择时取决于最频繁的查询SQL的条件,如果某个表的数据有明显的时间特征,则通常适合使用时间范围分片。

可以将原来的连接分解为多个单表查询,然后在用户程序中进行连接。

优点:

  • 不存在单个库大数据和高并发的性能瓶颈
  • 应用端改造少
  • 提高系统稳定性和负载能力

缺点:

  • 事务一致性难以解决
  • 跨节点Join性能差,逻辑复杂
  • 数据多次扩展难度和维护量极大

很多大表对MySQL这种关系型数据库的需求并不大,并不要求ACID,可以考虑将这些表歉意到NoSQL,解决水平扩展问题。如日志类、监控类、统计类数据,非结构化或若结构化数据,对事务要求不强的数据等。

垂直切分

将一张表按列切分成多个表,将数据库中标的密集程度部署到不同的库中。如电商数据库切分成商品数据库和用户数据库。当一张表的字段过多时考虑垂直拆分,通常将一张表的字段拆分为主表和扩展表。

主从复制

主从复制是用来建立一个和主数据库完全一样的数据库环境,称为从数据库。主数据库一般是准实时的业务数据库。

好处:

  1. 主数据库服务器故障后,可以切换到从数据库继续工作,避免数据丢失。
  2. 架构扩展,业务量越来越大,做多库的存储,降低磁盘I/O访问的频率,提高单个机器的I/O性能。
  3. 读写分离,是数据库能支撑更大的并发。

原理:

  1. 主数据库的更新事件被记录到二进制日志(bin-log)中
  2. 从库连接到主库
  3. 主库创建一个binlog dump thread线程,把binlog内容发送到从库
  4. 从库启动,创建I/O线程,读取主库传过来的binlog内容并写到relay log
  5. 从库创建一个SQL线程,从relay log读取内容,将更新内容写入到从库。

读写分离

基本原理是让主数据库处理增删改操作,从数据库处理查询操作。数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库。

大多数业务往往读多写少,这时候数据库的读性能就会成为性能瓶颈。

为了解决读的性能瓶颈,有多种解决方案。

  1. 使用缓存 读写分离通过多个读库,分摊了数据库读的压力。通过缓存的使用,减少了数据库读的压力。缓存的使用成本更低,开发更容易。但是,缓存如果挂掉,数据库也会挂掉。读写分离可以看做是缓存都解决不了时的一种解决方案。
  2. 水平切分 水平切分主要解决的是数据容量的瓶颈。例如订单表,数据只增不减,历史数据又必须留存,非常容易成为性能的瓶颈。当单库的容量成为了瓶颈,希望提高数据库的写性能,降低单库容量,就可以采用水平切分。