深入浅出MySQL

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 本文链接说明索引的设计和使用设计索引原则小常识BTRee索引SQL中的安全问题常用SQL技巧SQL优化过程了解SQL执行频率EXPLAIN分析查看索引使用情况定期分析表和检查表定期优化表常用sql优化优化数据库对象优化表的数据类型通过拆分表提高表的访问效率使用中间表提供统计查询速度锁问题锁M


0 本文链接

本站:
深入浅出MySQL

个人博客:
深入浅出MySQL

1 说明

  • 之前先看了《MySQL必知必会》一书,再来看这本。二者很多内容相同,所以本书只节选了部分内容来看。
  • 链接: MySQL必知必会

2 索引的设计和使用

1 设计索引原则

  • 最适合索引的列是出现在WHERE/JOIN/ORDER BY/GROUP BY/DISTINCT中的列。而不是出现在SELECT中的列。
  • 使用唯一索引。索引的基数越大,效果越好。比如存放身份号码的列具有不同的值,很容易区分各行。而用来记录性别的列,只含有’M’和’F’,对这样的列进行索引,没多大用处。
  • 使用短索引。如果索引的值很长,那么查询的速度会受到影响。
  • 利用最左索引。假设建立了user_id, user_name, status(按该顺序建立)复合索引, 实际上是创建了三个MySQL可利用的索引。

    user_id, user_name, status
    user_id, user_name
    user_id

    只要在查询中指定了user_id的值,无论是否有user_name或者status,MySQL都可以使用这个索引。但是,如果不包含user_id,只包含了user_name或status,那么,MySQL不能利用这个索引。

  • 限制索引的数目。并非索引越多越好。除了要占用额外的磁盘空间外,还会降低写操作的性能。在修改表的时候,索引必须进行更新,索引越多,所花的时间也越多。
  • 删除不再使用或者很少使用的索引。从而减少对表更新操作的影响。

2 小常识

  • 系统自动创建primary key的索引。
  • 系统自动创建unique key的索引。

3 BTRee索引

  • MyISAM和InnoDB默认创建的都是BTRee索引。
  • 当使用>,<,>=,<=,BETWEEN,!=,<>或者LIKE 'pattern'(pattern不以通配符开始)操作符时,都可以使用相关列上的索引。

3 SQL中的安全问题

  • SQL注入攻击

    -- 通过账号密码获取账号id, 如果存在,则登陆成功
    SELECT id FROM account WHERE user_name='admin' AND password='opds123456';

    但如果没有任何过滤,传入的user_name=admin'#,就会出现大问题。

    SELECT id FROM account WHERE user_name='admin '# ' AND password='123456';

    因为#会将后面的句子注释。因此密码验证功能就丢失了。

    又如传入user_name=admin' OR 1=1

    SELECT id FROM account WHERE user_name='admin' OR 1=1 AND password='123456';
  • 防范

    • 使用PrepareStatement,预编译sql后,通过绑定参数来执行。
    • 对特殊字符进行转换

      // MySQL C API
      mysql_real_escape_string() 
      // PHP
      mysql_real_escape_string()
    • 定义函数对输入进行校验。

4 常用SQL技巧

  • 使用RAND()获取随随即行

    SELECT * FROM user ORDER BY RAND() LIMIT 10;

5 SQL优化过程

1 了解SQL执行频率

使用SHOW [SESSION | GLOBAL] STATUSA来获得服务器状态信息。
SESSION表示当前连接,如果直接写SHOW STATUS, 则默认是SESSION。
GLOBAL表示从数据库上次启动至今的统计结果。

SHOW GLOBAL STATUS LIKE 'Com_%';

在结果中可以看下

Com_select: 执行select操作次数
Com_insert: 执行insert操作次数,批量插入只累加1
Com_update: 执行update操作次数
Com_delete: 执行delete操作次数

通过以上信息,就可以了解到是以更新为主还是查询为主了。

2 EXPLAIN分析

假设有一张account表存在id(主键), phone字段,且未对phone建立索引。

通过id查找:

EXPLAIN SELECT * FROM account WHERE id = 10839792;

输出:

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE      account const   PRIMARY         PRIMARY 4   const   1   NULL

我们可以看到,rows=1, 表示扫描了1行接得到了结果。

通过phone查找:

EXPLAIN SELECT * FROM account WHERE phone = 1899713xxxx;

输出:

id  select_type table   type    possible_keys   key key_len ref rows    Extra
1   SIMPLE  account     ALL     NULL    NULL    NULL    NULL    48130   Using where

可以看到,扫描了48130行才得到想要的结果。

如果这个查询很常用,就有必要对phone建立索引,特别是account表很庞大的时候,速度优势很明显。

EXPLAIN显示了MySQL如何使用索引来处理SELECT语句以及连接表。可以帮助选择更好的引擎和写出更佳的查询语句。

  • select_type, SELECT的类型,有以下几种值:

    • SIMPLE: 表示简单的SELECT,没有UNION和子查询。
    • PRIMARY: 查询中包含任何复杂的子查询, 最外层被标记为PRIMARY。
      示例a

      EXPLAIN 
      SELECT a.role_id, a.role_name, (SELECT SUM(money) AS total_recharge 
      FROM user_pay b WHERE a.role_id = b.role_id) FROM user a;
    • SUBQUERY: 在示例a中, 子句就被标记为SUBQUERY。
    • DERIVED: 在FROM列表中的子查询被标记为DERIVED。MySQL会将这个子查询的结果放到一个临时表中,相当于该临时表是从子查询中派生出来的。

      EXPLAIN 
      SELECT t.role_id, t.role_name FROM (SELECT role_id, role_name FROM user LIMIT 10) t;
    • UNION: 若第二个SELECT出现在UNION之后,就会被标记为UNION。
      示例b

      EXPLAIN
      SELECT role_id FROM user_pay WHERE money=6 
      UNION
      SELECT role_id FROM user_pay WHERE money=30;
    • UNION RESULT: 从UNION获取结果被标记为UNION RESULT。见示例b的结果。
    • SUBQUERY和UNION还可以被标记为DEPENDENT和UNCACHEABLE
      • DEPENDENT: 意味着子句依赖于外层发现的结果,见示例a
      • UNCACHEABLE: 意味着SELECT某些特性阻止结果缓存于一个item_cache中(TODO:需要更多的资料)。
  • type, 表示在表中找到所需行的方式,也称访问类型。

    • 常见有ALL, index, range, ref, eq_ref, const, system, NULL, 从左到右,性能从最差到最好。
    • NULL: 执行时甚至不用访问表或使用索引,比如找出找出最小用户id(主键)

      EXPLAIN 
      SELECT MIN(role_id) FROM user;
    • const: 表中最多只有一个匹配行,用于primary key或者unique索引。因为只匹配一行,所以速度快。

      EXPLAIN 
      SELECT * FROM user WHERE role_id=123;
    • system: 是const的特殊类型,为表中只有一行的时候。
    • eq_ref: 简单的说就是在多表连接中使用primary key和unique key做为关联条件。

      -- role_id是user和user_activity的主键
      EXPLAIN 
      SELECT * FROM user, user_activity WHERE user.role_id=user_activity.role_id;
    • ref: 搜索时使用的索引不是primary key或unique,但可以是复合索引的第一个值。

      -- 存在复合索引 (channel_id, xx, xx)
      EXPLAIN 
      SELECT * FROM user WHERE channel_id=1;
    • 以上都是很理想的索引使用情况
    • range: 用索引来检索一定范围的行, BETWEEN, <, >。除此之外, IN, OR也会显示range,但性能有差异。

      -- level是索引,channel_id不是索引。
      -- type=range
      EXPLAIN SELECT role_id, name FROM user WHERE level BETWEEN 10 AND 30 AND channel_id < 10;
      -- type=all
      EXPLAIN SELECT role_id, name FROM user WHERE channel_id < 10;
    • index: 只查找索引,不能包含非索引值。

      -- level是索引,channel_id不是索引
      -- type=index
      EXPLAIN SELECT level FROM user;
      -- type=all
      EXPLAIN SELECT level, channel_id FROM user;
    • ALL: 将遍历全表以查找匹配的行。
  • possible_keys: 提示使用了哪些索引可以找到该行。
  • keys: 使用的索引。
  • key_len: 索引使用的长度。
  • ref: 哪些列或常量被用于查找索引列上的值。

    EXPLAIN 
    SELECT a.role_id, a.role_name, (SELECT SUM(money) AS total_recharge 
    FROM user_pay b WHERE a.role_id = b.role_id) FROM user a;

    输出:

    id  table   type    possible_keys   key     key_len ref     rows
    1   a       ALL     NULL            NULL    NULL    NULL    40      
    2   b       ref     role_id         role_id 8       test.a.role_id  3

    子句中使用a.role_id来查找匹配。

  • rows: 估算的找到所需的记录所需要读取的行数。
  • filtered: 显示了通过条件过滤出的行数的百分比估计值。
  • Extra: 比较重要的额外信息。

3 查看索引使用情况

SHOW STATUS LIKE 'Handler_read%';

输出:

Variable_name           Value
Handler_read_first      38
Handler_read_key        2044
Handler_read_last       0
Handler_read_next       988904
Handler_read_prev       0
Handler_read_rnd        2154
Handler_read_rnd_next   1022038
  • Handler_read_key: 这个值表示一个行被索引值读的次数,很低的值表示增加的索引对性能改善不高,因为索引并不经常使用。
  • Handler_read_rnd_next: 表示在数据文件中读下一行的请求书。如果值很高,表示进行了大量的扫描。通常说明索引不正确或写入的查询没有利用索引。
  • 按照搜索出的数据,这个库的索引情况并不理想。

4 定期分析表和检查表

  • 分析表 ANALYZE TABLE account;
  • 检查表 CHECK TABLE account;
    输出: 1 client is using or hasn't closed the table properly
    修复该错误:

    REPAIR TABLE account;

5 定期优化表

  • 优化表 OPTIMIZE TABLE account, user, …
  • 该命令可以将表中的空间碎片进行合并,并且可以消除由于删除或者更新造成的更新浪费。
  • 如果已经删除了表中的一大部分,或者对含有可变长度行的表进行了很多更改,可以使用该命令进行优化。
  • 该命令支队MyISAM,BDB,InnoDB起作用。

6 常用sql优化

  • 优化INSERT语句

    • 插入多行时,尽量使用多个值表的INSERT语句,减少客户端与服务端的链接,关闭消耗。

      INSERT INTO account VALUES(1, 2), (3, 4)...
    • 使用INSERT DELAYED INTO替代INSERT INTO。仅限于ISAM和MyISAM表。需要插入的数据会在内存中排队,直到有空闲。而客户端会立即得到OK响应。好处是极高的插入速度,客户端不需要等待太长的时间。坏处是如果没有来得及插入数据,在内存队列中的数据将会丢失,而且不能反悔自动递增ID。
    • 批量插入时,可以增加bulk_insert_buffer_size变量值来提高速度,只有MyISAM有效。这个参数是批量插入缓存大小,默认8M。
    • 使用LOAD DATA INFILE载入(未使用过)
  • 优化GROUP BY语句
    在包含了GROUP BY语句,会有一个默认的排序,如果没有必要对结果进行排序,可以用ORDER BY NULL取消排序。

    SELECT role_id, SUM(money) AS total_recharge FROM user_pay GROUP BY role_id ORDER BY NULL;
  • 优化ORDER BY语句
    当以下情况时,ORDER BY也可以借助索引来排序

    • 索引满足WHERE和ORDER BY
    • ORDER BY的顺序和索引顺序相同
    • ORDER BY都是升序或者降序的
      TODO 需要更多的资料
  • 优化嵌套查询

    • 使用JOIN来替代子查询,速度会快很多,尤其是对子查询中的列建有索引的情况下,性能会更好。
    • MySQL不需要再内存中创建临时表来完成这个逻辑上需要两个步骤的工作。
  • 使用SQL提示

    • USE INDEX: 添加希望MySQL去参考的索引列表

      SELECT * FROM user USE INDEX(user_id_phone) WHERE user_id=1 AND phone=123456;
    • IGNORE INDEX: 忽略指定的索引

      SELECT * FROM user IGNORE INDEX(user_id_phone) WHERE user_id=1 AND phone=123456;

      假设就这一个索引,忽略之后,MySQL会使用全表扫描

    • FORCE INDEX: 强制使用指定索引

      SELECT * FROM user FORCE INDEX(user_id_phone) WHERE user_id=1 AND phone=12356;

6 优化数据库对象

1 优化表的数据类型

  • 大利器PROCEDURE ANALYSE()
  • 用法 SELECT ... FROM ... WHERE ... PROCEDURE ANALYSE([max_elements,[max_memory]])
    • max_elements 默认值256, 查找每一列不同值时所需关注的最大不同值的数量。还用这个值来检查给予建议的值是否是ENUM。
    • max_memory 查找每一列所有不同值时可能分配的最大的内存数量。
  • 示例 SELECT * FROM user PROCEDURE ANALYSE(16, 256);,注意看Optimal_fieldtype中给予的建议。如果表中数据量小,要注意区分建议的局限性。

2 通过拆分表提高表的访问效率

对于MyISAM类型的表:
* 垂直拆分: 把主键和一些列放在一张表,把主键和另一些列放在另一张表。特别在一张表中,有的列常用,而有些列不常用的时候,可以这么拆。好处是使得数据行变小,查询的时候会减少I/O次数。缺点是查询所有数据的时候要JOIN操作。
* 水平拆分: 把很大的表拆成好几张,比如最近3个月的数据一张,3个月以前的表一张。

3 使用中间表提供统计查询速度

  • 如果表很大,要在上面对一定范围内的数据做查询统计。可以把这部分数据导出到另一张一模一样的表中,然后在做处理。
  • 这边省略了导出的时间。
  • 在新表上处理,不会对应用产生负面影响。
  • 可以在新表上增加索引,临时字段等,提供性能。

7 锁问题

1 锁

  • 表级锁: 开销小,加锁快,不会出现死锁,锁定粒度大,引起冲突概率最高,并发度最低。
  • 行级锁:开销大,加锁慢,会出现思索,锁定粒度最小,引起冲突概率最低,并发度最高。
  • 页面所: 会出现死锁,介于表级锁和行级锁中间。
  • MyISAM支持表级锁。
  • InnoDB支持表级锁和行级锁,默认采用行级锁。
  • 仅从锁的角度来说:
    • 表级锁更适合以查询为主,只有少量按索引条件更新数据的应用。
    • 行级锁适合有大量按索引条件并发更新数据的应用。

2 MyISAM表锁

  • 可以通过SHOW STATUS LIKE 'table_locks_%';来查看表锁定争夺。

    Variable_name           Value
    Table_locks_immediate   29029735
    Table_locks_waited      30

    如果Table_locks_waited值比较高,说明存在着较严重的表级锁竞争。

  • 加读锁: 不会阻塞其它用户对表的读操作,但是,会阻塞对该表的写操作,包括加锁的这个用户。
  • 加写锁: 只有加锁的这个用户可以对表进行读写操作,其它用户读写操作都会等待。
  • 示例:

    -- 加锁
    LOCK TABLE user WRITE;
    -- 可以进行读写操作
    -- 但其它用户则需要等待,比如另起终端执行读操作。
    SELECT * FROM user LIMIT 10;
    -- 直到释放了锁
    UNLOCK TABLES;
  • MyISAM会在SELECT前给锁设计的表添加读锁。在UPDATE,INSERT,DELETE时,加写锁。
  • MyISAM会一次获得所需要的锁,不会变更。比如获得了读锁,则不能执行写操作。
  • 并发插入
    • concurrent_insert 用于控制并发插入行为。SHOW VARIABLES LIKE 'concurrent_insert';
    • 当concurrent_insert=0的时候,不允许并发插入。
    • 当concurrent_insert=1的时候,表中间没有被删除的行,加读锁的时候,允许另一个用户从表尾插入数据,这个也是默认设置。
    • 当concurrent_insert=2的时候,无论表中间有没有删除的行,都允许另一个用户从表尾插入数据。
    • 我这边显示的是concurrent_insert=auto, 行为和concurrent_insert=1一样。
    • 可以在空闲的时候使用OPTIMIZE TABLE来整理空间碎片,收回因删除记录而产生的中间空洞。
  • 锁调度
    • MyISAM的读写是互斥的,串行的。
    • 假设同时有读和写请求到来,写会获得锁。
    • 即使读请求先在获取锁的等待队列中,写请求后到,写会获得锁。
    • 因为MySQL认为写操作比读操作重要。
    • 这也是MyISAM不适合有大量更新操作同时又有很多读操作的原因。
    • 大量的写操作会造成读操作难以获得锁。
    • 可以使用一些设置来调节这些行为:
      • 启动时指定参数low-priority-updates,使MyISAM默认给予读请求以优先的权利。
      • SET LOW_PRIORITY_UPDATES=1,使得该连接发出的写操作优先级降低。
      • 通过指定INSERT,UPDATE,DELETE语句的LOW_PRIORITY属性,降低该语句的优先级。
      • 给系统参数max_write_lock_count指定一定的值,当读请求达到这个值后,就将写请求的优先级降低。

3 InnoDB锁

TODO

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
5月前
|
存储 安全 关系型数据库
MySQL进阶(二)
MySQL进阶(二)
27 0
|
5月前
|
存储 监控 关系型数据库
MySQL进阶(一)
MySQL进阶(一)
28 0
|
5月前
|
SQL 关系型数据库 MySQL
Mysql从入门到精通——Mysql知识点总结(基础篇)
Mysql从入门到精通——Mysql知识点总结(基础篇)
|
存储 缓存 关系型数据库
132.【MySQL_进阶】(十)
132.【MySQL_进阶】
130 1
|
存储 SQL 关系型数据库
132.【MySQL_进阶】(二)
132.【MySQL_进阶】
69 0
|
6月前
|
存储 关系型数据库 MySQL
【MySQL进阶之路丨第一篇】什么是MySQL?
【MySQL进阶之路丨第一篇】什么是MySQL?
52 0
|
存储 SQL 关系型数据库
132.【MySQL_进阶】(一)
132.【MySQL_进阶】
60 0
|
SQL 关系型数据库 MySQL
132.【MySQL_进阶】(五)
132.【MySQL_进阶】
123 0
|
存储 SQL Java
132.【MySQL_进阶】(七)
132.【MySQL_进阶】
108 0
|
存储 SQL 监控
【MySQL新手入门系列五】:MySQL的高级特性简介及MySQL的安全简介
MySQL 作为一款业界领先的数据库管理系统,具备许多高级特性,本文将重点介绍MySQL中的索引、视图、存储过程、触发器和事务处理。