我的MySQL优化之路

一、索引

1. 索引是什么?
  1. 定义
    索引是帮助MySQL高效获取数据的数据结构。
    可以得到索引的本质:索引是数据结构

    可以简单理解为索引是:排好序的快速查找数据结构

  2. 详细解释
    在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。
    为了加快数据的查找,可以维护一个二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录。

  3. 结论
    数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构就是索引。

  4. MySQL的索引
    我们平常所说的索引,如果没有特别指明,都是指B树(多路搜索树,并不一定是二叉的)结构组织的索引。其中聚集索引,次要索引,覆盖索引,复合索引,前缀索引,唯一索引默认都是使用B+tree索引,统称索引。当然,除了B+树这种类型的索引之外,还有哈稀索引(hash index)等。

2. 索引的优势与劣势
  1. 优势
    类似大学图书馆建书目索引,提高数据检索的效率,降低数据库的IO成本
    通过索引列对数据进行排序,降低数据排序的成本,降低了CPU的消耗

  2. 劣势
    实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。
    因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件每次更新添加了索引列的字段,
    都会调整因为更新所带来的键值变化后的索引信息索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句。

3. 那些情况需要创建索引?
  1. 主键自动建立唯一索引
  2. 频繁作为查询条件的字段应该创建索引
  3. 查询中与其它表关联的字段,外键关系建立索引
  4. 频繁更新的字段不适合创建索引
  5. Where条件里用不到的字段不创建索引
  6. 单键/组合索引的选择问题,(在卨并发下倾向创建组合索引)
  7. 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
  8. 查询中统汁或者分组字段
4. 那些情况不需要创建索引
  1. 表记录太少

  2. 经常增删改查的表

  3. 包含许多重复的内容的列
    注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。
    数据重复分布平均的表字段,因此应该只为经常查询和最经常排序的数据列建立索引。 注意,如果某个数据列包含许多重复的内容,为它建立索引就没有太大的实际效果。

    假如一个表有10万行记录,有个字段A只有T和F两种值,且每个值的分布概率大约为50%,那么这种表A字段建索引一般不会提高数据库的查询速度。
    索引的选择性是指索引列中不同值的数目与表中记录数的比。如果一个表中有2000条记录,表索引列有1980个不同的值,那么这个索引的选择性就是1980/2000=0.99。 一个索引的选择性越接近1,这个索引的效率就越高。

二、MySQL的查询优化器

1. 什么是优化器
  1. MySQL中有专门负责优化SELECT语句的优化器模块。
    主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的 Query 提供他认为最优的执行计划(他认为最优的数据检索方式,但不见得是 DBA 认为是最优的,这部分最耗费时间)。
  2. 当客户端向 MySQL 请求一条 Query ,命令解析器模块完成请求分类,区别出是 SELECT 并转发给 MySQL Query 即 optimizer 时, MySQL Query optimizer 首先会对整条 Query 进行优化,处理掉一些常量表达式的预算,直接换算成常量值。并对 Query 中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等.然后分析 Query 中的 Hint 信息(如果有),看显示 Hint 信息是否可以完全确定该 Ouery 的执行计划.如果没有 Hint 或 Hint 信.息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,根据 Query 进行写相应的计算分析,然后再得出最后的执行计划。
2. MySQL的常见瓶颈
  1. CPU : CPU 在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候
  2. I/O : 磁盘I/O瓶颈发生装入数据远大于内存容量的时候
  3. 服务器硬件的性能瓶颈: top, free, iostat 和 vmstat 来查看系统的性能状态

三、MySQL的执行计划

1. 什么是执行计划

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

2. 执行计划能做什么
  • 表的读取顺序
  • 数据读取操作的操作类型
  • 哪些索引可以使用
  • 哪些些索引被实际使用
  • 表之间的引用
  • 每张表有多少行被优化器查询
3. 使用查询优化器

使用方法

Explain [sql语句]

执行计划包含的信息:

id select_type table type possible_keys key key_len ref rows EXtra

各个字段解析如下

  1. id
    select 查询的序列号,包含一组数字,表示查询中执行 select 子句或操作表的顺序

    id 相同 执行顺序由上至下
    id 不同 如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
    id 相同不同,同时存在 先执行id值较大的,相同的id值,从到下,依次执行。

  2. select_type
    查询的类型,主要用于区别普通查询,联合查询,子查询等的复杂查询。
    主要有以下几种类型:SIMPLEPRIMARYSUBQUERYDERIVEDUNIONUNION RESULT

    1. SIMPLE 简单的 select 查询,查询中不包含子查询或UNION
    2. PRIMARY 查询中若包含任何复杂的子部分,最外层查询则被标记
    3. SUBQUERY 在 SELECT 或 WHERE 列表中包含了子查询
    4. DERIVED 在 FROM 表中,包含的子查询被标记为 DERIVED (衍生)
      MysQL 会递归执行这些子查询,把结果放在临时表里。
    5. UNION 若第二个 SELECT 出现在 UNION 之后,则被标记为 UNION;
      若 UNION 包含在 FROM 子句的子查询中,外层 SELECT 将被标记为: DERIVED
    6. UNION RESULT 从 UNION 表获取结果的 SELECT
  3. type
    显示查询使用了何种类型,
    主要有以下几种类型:Allindexrangerefeq_refconst / systemNULL
    从最好到最差依次是:

    system > const > eq_ref > ref > range > index > All

    一般来说,得保证查询至少达到range级别,最好是ref

    类型 说明
    System 表示只有一行记录(等于系统表),这是 const 类型的特列,平时不会出现,这个也可以忽略不计
    const 表示通过索引一次就找到了,const用于 Primary key 或者 unique 索引。因为只匹配一行数据,所以很快。
    如将上键置于 where 列表中, MySQL 就能将该查询转换为一个常量
    eq_ref 唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描
    ref 非唯一性索引扫描,返回匹配某个单独值的所有行。
    本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属属于查找和扫描的混合体
    range 只检索给定范围的行,使用一个索引来选择行。key 列显示使用了哪个索引。一般就是在你的where 语句中出现了 between 、<、>、 in 等的查询。
    这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束于另一点,不用扫描全部索引。
    index FullIndexScan, Index 和 ALL 区别为 Index 类型只遍厉索引树。这通常比ALL快,因为索引文件通常比数据文件小。
    (也就是说虽然 all 和 Index 都是读全表,但 index 是从索引中读取的,而 all 是从硬盘中读的)
    All Full Table Scan ,将遍历全表以找到匹配的行

    备注:一般来说,得保证杳询至少达到 range 级别,最好能达到ref

  4. possible_key 和 key

    1. possible_key
      显示可能应用在这张表中的索引,一个或多个。 查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被六询实际使川
    2. key
      实际使用的索引。如果为 NULL ,则没有使用索引。查询中若使用了覆盖索引,则该索引仅出现在 key 列表中
  5. key_len
    表示索引中引用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好。
    key_len显示的值为索引的最大可能长度,并非实际使用长度,即 key_len 是根据表定义计算而得,不是通过表内检索出的。

  6. ref
    显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。

  7. rows
    根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数

  8. Extra

    Using filesort 出现的话,问题就比较严重,需要了及时处理
    Using temporary 出现的话,问题就更严重了,需要立刻处理。
    Using index 表示结果很理想,

    类型 说明
    Using filesort 说明 MySQL 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作称为"文件排序"。
    Using temporary 使用了临时表保存中间结果,MySQL在对查询结果排序时候,使用临时表。常见于排序 order by 和分组查询 group by 。
    Using index 表示相应的 select 操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错。
    如果同时出现 using where ,表明索引被用来执行索引键值的查找;
    如果没有同时出现 using where ,表明索引用来读取数据而非执行查找动作。
    Using where 表明使用了where过滤
    Using join buffer 使用了连接缓存
    impossible where where 子句的值总是 false ,不能用来获取任何元组
    select tables optimized away 在没有 GROUPBY 子句的情况下,基于索引优化 MIN / MAX 操作或者对于 MyISAM 储引擎优化 COUNT( * )操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
    distinct 优化 distinct 操作,在找到第一匹配的元组后即停止找同样值的动作。

四、索引

1. 覆盖索引

覆盖索引(Covering Index),或被称为索引覆盖

理解方式一:就是 select 的数据列只用从索引中就能够取得,不必读取数据行,MySQL 可以利用索引返回 select 列表中的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖

理解方式二:索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含了(或覆盖了)满足查询结果的数据就叫做覆盖索引。

注意:如果要使用覆盖索引,一定要注意 select 列表中只取出需要的列,不可select *, 因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降。

与人交流的时候或者自己调优的时候,着重从idtypekeyrowsExtra这个几个方面着手,这是最重要的字段属性。

2. 多表如何设计索引

尽可能减少join语句中的NestedLoop的循环总次数;“永远用小结果集驱动大的结果集”。
优先优化NestedLoop的内层循环;
保证Join语句中被驱动表上Join条件字段已经被索引。
当无法保证被驱动表的Join条件字段被索引且内存资源充足的前提下,不要太吝啬JoinBuffer的设置。

3. 避免索引失效的操作
  1. 全值匹配我最爱
    如果建立了多列索引,全部按照索引建立字段进行查询。
  2. 最佳左前缀法则
    如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并不跳过索引的中间列。
  3. 不在索引上做任何操作(计算、函数、(自动 or 手动)类型转换),否则会导致索引失效而转向全表扫描。
  4. 存储引擎不能使用索引中范围条件右边的列
    复合索引中,使用范围查询列的右边的字段,无法再使用索引。
  5. 尽量使用覆盖索引(只访问索引的查询(索引列和查询列一致)),减少 select *
  6. MySQL 在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描
  7. is nullis not null的条件查询,也无法使用索引
  8. like以通配符开头'%abc...'MySQL 索引失效会变成全表扫描的操作
    以通配符开头'abc...'索引的当前列,会使用范围索引。
    解决like中的两边%号索引失效的办法就是使用覆盖索引。
  9. 字符串不加单引号索引失效。
  10. 少用 or,用它来连接时会索引失效。

可以简单的这么理解

带头大哥不能死
中间兄弟不能断
索引列上无计算
like % 加右边
范围之后全失效
字符串里有引号

实例:
假设index(a,b,c)。

Where语句 是否使用索引 使用的索引
where a=3 使用到a
where a=3 and b=5 使用到a,b
where b=3
where b=3 and c=4
where c=4
where a=3 and c=5 部分使用 使用到a。c没有使用,因为b中间断了
where a=3 and b>4 and c=5 部分使用 使用到a和b。c没有被使用,因为c在范围之后
where a=3 and b like 'kk%' and c=4 使用到a,b,c
where a=3 and b like '%kk' and c=4 只用到a
where a=3 and b like '%kk%' and c=4 只用到a
where a=3 and b like 'k%kk%' and c=4 使用了a,b,c
4. 索引总结
  • 对于单键索引,尽量选抒针对当前 query 过滤性更好的索引
  • 在选择组合索引的时候,当前 Query 中过滤性最好的字段在索引字段顺序中,位置越靠前越好
  • 在选择组合索引的时候,尽量选择可以能够包含当前 query 中的 where 字句中更多字段的索引
  • 尽可能通过分析统计信息和调整 query 的写法来达到选抒合适索引的目的

【 优化总结口诀 】
全值匹配我最爱,最左前缀要遵守;
带头大哥不能死,中间兄弟不能断;
索引列上少计算,范围之后全失效;
KIKE百分写最右,覆盖索引不写星;
不等空值还有or,索引失效要少用;
VAR 引号不可丢,SQL 高级也不难!

其他:

group by的字段基本上都需要进行排序,可能会产生临时表。
group by的优化规则基本上与order by一致

五、查询优化

1. 发现问题步骤
  1. 观察,至少跑 1 天,看看生产的慢SQL情况。
  2. 开启慢查询日志,设置阀值,比如超过 5 秒钟的就是慢SQL ,并将它抓取出来。
  3. explain + [慢 SQL 分析]
  4. 使用show profile分析
  5. 运维经理 or DBA ,进行 SQL 数据库服务器的参数调优。

简单总结如下:

  • 慢查询的开启并捕获
  • explain +慢 SQL 分析
  • show profile 查询 SQL 在 MySQL服务器里面的执行细节和生命周期情况
  • SQL 数据库服务器的参数调优。
2. in和exist的使用

尽量保证:小表驱动大表,即小的数据集驱动大的数据集
for 理解为循环。
示例:

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优于exist

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

当A表的数据集小于B表的数据集时候,用exist优于in
注意:A表与B表的id字段应当建立索引。

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

提示:

  • EXISTS ( subquery )只返回 TRUE 或 FALSE ,因此子查询中的SELECT *也可以是SELECT 1SELECT 'x',官方说法是实际执行时会忽路 SELECT 清单,因此没有区别
  • EXISTS 子查询的实际执行过程可能经过了优化,而不是我们理解上的逐条对比,如果担优效率问题,可进行实际检验以确定是否有效率问题。
  • EXISTS 子查询往往也可以用条件表达式、其他子查询或者 JOIN 来替代,何种最优需要具体问题具体分析
3. order by 关键字优化
  1. 排序方式
    MySQL 支持二种方式的排序,FileSortIndex
    Index效率高,它指 MySQL 扫描索引本身完成排序。
    FIlesort方式效率较低。
    OROERB BY 满足两种情况,会使用Index方式排序:

    • OROERB BY 语句使用索引最左前列
    • 使用 Where 子句与 Order By 子句条件列组合满足索引最左前列
  2. 双路排序
    MySQL4.1之前是使用双路排序,字面意思就是两次扫码磁盘,最终得到数据。
    读取行指针和order by列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出。
    总结:
    从磁盘取排序字段,在buffer进行排序,再从磁盘去其他字段。

  3. 单路排序
    从磁盘读取查询需要的所有列,按照order by列在 buffer 对它们进行排序,然后扫描排序后的列表进行输出, 它的效率更快一些,避免了第二次读取数据。并且把随机
    IO变成了顺序IO,但是它会使用更多的空间,因为它把每一行都保存在内存中了。

  4. 提高 Order By 的速度

    1. Order by 时select *是一个大忌,只 Query 需要的字段,这点非常重要。在这里的影响是:

      当 Query 的字段大小总和小于 max_length_for_sort_data 而且排序字段不是 TEXT|BLOB 类型时,会用改进后的算法一一单路排序,否则用老算法一一多路排序。
      两种算法的数据都有可能超出 sort_buffer 的容量,超出之后,会创建tmp文件进行合并排序,导致多次I/O,但是使用单路排序算法的风险会更大一些,所以要提高 sort_buffer_size

    2. 尝试提高sort_buffer_size不管用哪种算法,提高这个参数都会提高效率,当然,要根据系统的能力去提高,因为这个参数是针对每个进程的。

    3. 尝试提高max_length_for_sort_data提高这个参数,会增加用改进算法的概率。但是如果设的太高,数据总容量超出sort_buffer_size的概率就增大,明显症状是高的磁盘 I/O 活动和低的处理器使用率。

  1. 为排序使用索引
    MySQL 两种排序方式:文件排序或扫描有序索引
    MySQL 能为排序查询使用相同的索引
    示例:
    key a_b_c (a,b,c)

    order by 能使用索引最左前缀

    ORDER BY a
    ORDER BY a,b
    ORDER BY a,b,c
    ORDER BY a,DESC,b DESC,c DESC

    如果 WHERE 使用索引的最左前缀定义为常量,则 order by 能使用索引

    WHERE a=const ORDER BY b,c
    WHERE a=const AND b=const ORDER BY c
    WHERE a=const ORDER BY b,c
    WHERE a=const AND b>const ORDER BY b,c

    不能使用索引进行排序

    ORDER BY a ASC, b DESC, c DESC /* 排序不一致 /
    WHERE g=const ORDER BY b,c /
    丢失a索引 /
    WHERE a=const ORDER BY c /
    丢失b索引 /
    WHERE a=const ORDER BY a,d /
    d不是索引的一部分 /
    WHERE a in (...) ORDER By b,c /
    对于排序来说,多个相等条件也是范围查询 */

4. group by 关键字优化
  • group by实质是先排序后进行分组,遵照索引建的最佳左前缀原则。
  • 当无法使用索引列,可选择增大max_length_for_sort_data参数的设置 + 增大sort_buffer_size参数的设置。
  • where 高于 having,能写在where限定的条件就不要去having限定了。

六、MySQL的慢查询日志

1. 什么是慢查询日志

MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。
具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为10,意思是运行10秒以上的语句会被记录。

由他来查看哪些SQL超出了我们的最大忍耐时间值,比如一条sq|执行超过5秒钟,我们就算慢SQL,希望能
收集超过5秒的sq|,结合之前explain进行全面分析。

这个是由参数long_query_time控制,默认情况下long_query_time的值为10秒,
命令:

SHOW VARIABLES LIKE 'long_query_time%';

可以使用命令修改,也可以在my.cnf参数里面修改。

假如运行时间正好等于long_query_time的情况,并不会被记录下。也就是说,
在mysqli源码里是判断大于long_query_time,而非大于等于。

可以使用MySQL的sleep操作来模拟慢查询

select sleep(4);
2.慢日志分析

分析慢日志的方式有一下两种,都是MySQL自带的分析工具,分别是mysqldumpslowshow profile

3. 日志分析工具mysqldumpslow

在生产环境中,如果要手工分析日志,查找、分析SQL,显然是个体力活,MySQL提供了日志分析工具
mysqldumpslow

  1. 查看mysqldumpslow的帮助信息
    查看命令:

    mysqldumpslow --help
    

    帮助信息如下:

    s:是表示按照何种方式排序;
    c:访问次数
    l:锁定时间
    r:返回记录
    t:查询时间
    al:平均锁定时间
    ar:平均返回记录数
    at:平均查询时间
    t:即为返回前面多少条的数据;
    g:后边搭配一个正则匹配模式,大小写不敏感的;

  2. 工作常用参考
    得到返回记录集最多的10个SQL

    mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log
    

    得到访问次数最多的10个SQL

    mysqldumpslow -s c -t 10 /var/lib/mysql/atguigu-slow.log
    

    得到按照时间排序的前10条里面含有左连接的查询语句

    mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/atguigu-slow.log
    

    另外建议在使用这命令时结合]和more使用,否则有可能出现爆屏情况

    mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log | more
    
4. show profile日志分析
  1. 什么是show profile
    是MySQL提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于SQL调优的测量
    详情可以参考官网地址
    默认情况下,参数处于关闭状态,并保存最近15次的运行结果

  2. 分析步骤

    1. 是否启用,看看当前的MySQL版本是否支持
      show variables like 'profiling';
      
      默认是关闭,使用前需要开启
      或者
      show variables like 'profiling%';
      
    2. 开启功能,默认是关闭,使用前需要开启冒
      set profiling=on;
      
    3. 运行SQL
      select * from tb_orders group by id limit 50000;
      select * from tb_orders group by id order by 5;
      
    4. 生查看结果;
      show profiles;
      
    5. 诊断SQL
      show profile cpu, block io for query [上一步前面的问题SQL数字号码]
      
    type 说明
    ALL 显示所有的开销信息
    BLOCKIO 显示块℃相关开销
    CONTEXTSWITCHES 上下文切换相关开销
    CPU 显示CPU相关开销信息
    IPC 显示发送和接收相关开销信息
    MEMORY 显示内存相关开销信息
    PAGE FAULTS 显示页面错误相关开销信息
    SOURCE 显示和Source_function,Source_file,Source_line相关的开销信息
    SWAPS 显示交换次数相关开销的信息
    1. 日常开发需要注意的结论
    • converting HEAP to MyISAM
      查询结果太大,内存都不够用了往磁盘上搬了。
    • Creating tmp table
      创建临时表
      拷贝数据到临时表
      用完再删除
    • Copying to tmp table on disk
      把内存中临时表复制到磁盘,危险!!!
    • locked
5. 全局查询日志
  1. 配置启用
    在mysqlfimy.cnf中,设置如下:
    #开启
    general_log=1
    #记录日志文件的路径
    general_log_file=/path/logflle
    #输出格式
    log_output=FlLE
    
    此后,你所编写的sq|语句,将会记录到里的/path/logflle文件中。
  2. 编码启用
    命令:
    set global general_log=1
    
    set gIobal log_output=TABLE
    
    此后,你所编写的sq|语句,将会记录到里的general_log表,可以用下面的命令查看
    select from mysql.general_log
    

七、MySQL锁机制

1. 锁的概述
  1. 锁的定义
    锁是计算机协调多个进程或线程并发访问某一资源的机制:
    在数据库中,除传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源:如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素:从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。

  2. 锁的分类

    • 对数据操作的类型来分有:读锁写锁

      读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响。
      写锁(排它锁):当前写操作没有完成前,它会阻断其他写锁和读锁。

    • 从对数据操作的粒度来分有:表锁行锁

2. 表锁
  1. 表锁特点
    偏向MyISAM存储引擎,开销小,加锁快;无死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。

  2. 手动增加表锁
    语法:

    lock table [表1名字] [锁类型 read/write], [表2名字] [锁类型 read/write] 
    
    lock table mylock read, book write;
    
  3. 查看表上加过的锁

    show open tables;
    
  1. 释放表锁

    unlock tables;
    
  2. 读锁和写锁
    MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁。
    MySQL的表级锁有两种模式:
    表共享读锁(Table Read Lock)
    表独占写锁(Table Write Lock)

    锁类型 可否兼容 读锁 写锁
    读锁
    写锁

    结合上表,所以对MyISAM表进行操作,会有以下情况:
    1、对MyISAM表的读操作(加读锁),不会阻塞其他进程对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其它进程的写操作:
    2、对My|SAM表的写操作(加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其它进程的读写操作。

    重点:简而言之,就是读锁会阻塞写,但是不会堵塞堵。而写锁则会把读和写都堵塞。

  3. 表锁分析
    【如何分析表锁定】
    可以通过table_locks_waitedtable_locks_immediate状态变量来分析系统上的表锁定:

    show status like 'table%';
    

    这里有两个状态变量记录MySQL内部表级锁定的情况,两个变量说明如下:
    TabIe_locks_immediate
    产生表级锁定的次数,表示可以立即获取锁的查询次数,每立即获取锁值加1。
    Table_locks_waited
    出现表级锁定争用而发生等待的次数(不能立即获取锁的次数,每等待一次锁值加1),此值高则说明存在着较严重的表级锁争用情况。

    此外,MyISAM的读写锁调度是写优先,这也是MyISAM不适合做写为主表的引擎。因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞。

2. 行锁
  1. 行锁特点
    偏向InnoDB存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
    InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁。
  2. 什么是事务
    事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性:
    • 原子性(Atomicity):
      事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
    • 一致性(Consistent):
      在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。
    • 隔离性(Isolation):
      数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行;这意味着事务处理过程中的中间状态对外部是不可见的,反之苏然。
    • 持久性(DurabIe):
      事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。
3. 并发事务处理带来的问题
  1. 更新丢失(Lost Update)
    当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题一一最后的更新覆盖了由其他事务所做的更新。

    例如,两个程序员修改同一java文件:每程序员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。最后保存其更改副本的编辑人员覆盖前一个程序员所做的更改。
    如果在一个程序员完成并提交事务之前,另一个程序员不能访问同一文件,则可避免此问题。

  2. 脏读(Dirty Reads)
    一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做”脏读”

    一句话:事务A读取到了事务B已修改但尚未提交的的数据,还在这个数据基础上做了操作;此时,如果B事务回滚,A读取的数据无效,不符合一致性要求。

  3. 不可重复读(Non-Repeatable Reads)
    一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。

    一句话:事务A读取到了事务B已经提交的修改数据,不符合隔离性。

  4. 幻读(Phantom Reads)
    一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读

    一句话:事务A读取到了生务B体提交的新增数据,不符合隔离性:

    多说一句:幻读和脏读有点类似,
    脏读是事务B里面修改了数据,
    幻读是事务B里面新增了数据。

4. 事务隔离级别
  1. 事务隔离级别
    “脏读”,“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决:
    读数据一致性及允许的开发副作用
    隔蒿级别
    读取数据一直性 脏读 不可重复读 幻读
    未提交读(Readuncommitted) 最低级别,只能保证不读取物理上陨坏的数据
    已提交度(Readcommitted) 语句级
    可重0读(Repeatableread) 事务级
    可庳列化(Serializable) 最高级别,事务级
数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的:同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。

常看当前数据库的事务隔离级别
```sql
show variables like 'tx_isolation'
```
  1. 行锁失效
    无索引或是索引失效,会引起行锁变表锁
5. 间隙锁
  1. 什么是间隙锁
    当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”
    InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。

  2. 间隙锁的危害:
    因为Query执行过程中通过过范围查找的话,他会锁定整个范围内所有的索引键值,即使这个键值并不存在。
    间隙锁有一个比较致命的弱点,就是当锁定一个范围键值之后,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据;在某些场景下这可能会对性能造成很大的危害。

6. 如何锁定一行

面试题:常考如何锁定一行

begin;
select * from test where id=8 for update;
commit;
7. 行锁表锁对比

Innodb存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高一些,但是在整体并发处理能力方面要远远优于MyISAM的表级锁定的。
当系统并发量较高的时候,Innodb的整体性能和MyISAM相比就会有比较明显的优势了。
但是,Innodb的行级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让Innodb的整体性能表现不仅不能比MyISAM高,甚至可能会更差。

8. 行锁分析

【如何分析行锁定】
通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况

show status like 'innodb_row_lock%',

示例:

mysql> show status like 'innodb_row_lock%';
+-------------------------------+-------+
| Variable_name                 | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0     |
| Innodb_row_lock_time          | 87647 |
| Innodb_row_lock_time_avg      | 12521 |
| Innodb_row_lock_time_max      | 23240 |
| Innodb_row_lock_waits         | 7     |
+-------------------------------+-------+

对各个状态量的说明如下:
lnnodb_row_lock_current_waits:当前正在等待锁定的数量;
lnnodb_row_lock_time:从系统启动到现在锁定总时间长度;
lnnodb_row_lock_time_avg:每次等待所花平均时间;
lnnodb_row_lock_time_max:从系统启动到现在等待最常的一次所花的时间;
lnnodb_row_lock_waits:系统启动后到现在总共等待的次数;
对于这5个状态变量,比较重要的主要是

lnnodb_row_lock_time_avg(等待平均时长)
lnnodb_row_lock_waits(等待总次数)
lnnodb_row_lock_time(等待总时长)这三项;
尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手指定优化计划。

优化建议:
尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁。
合理设计索引,尽量缩小锁的范围
尽可能较少检索条件,避免间隙锁
尽量控制事务大小,减少锁定资源量和时间长度
尽可能低级别事务隔离

  1. 页锁
    开销和加锁时间界于表锁和行锁之间。会出现死锁。锁定粒度界于表锁和行锁之间,并发度一般。

八、主从复制

原理图:


我的MySQL优化之路_第1张图片
主从复制原理图.png

这里只对原理进行讲解,具体的安装步骤,可以看我的其他的文章

MySQL复制过程分成三步:

  1. master将改变记录到二进制日志(binarylog)。这些记录过程叫做二进制日志事件,binarylogevents。
  2. slave将master的binary log events拷贝到它的中继E志(relay log)
  3. 引ave重做中继日志中的事件,将改变应用到自己的数据库中。MySQL复制是异步的且串行化的。

九、其他总结

  • select 加载,先从from开始读。

你可能感兴趣的:(我的MySQL优化之路)