MySql优化那些事

MySql如何工做

Mysql客户端和服务器之间的通讯协议是“半双工”的,意味着在任何一个时刻,要么是由服务器向客户端发送数据,要么是有客户端向服务器发送数据,这两个动做不能同时发生。当咱们发送一条SQL命令引擎时,MySql执行过程以下图所示
图片描述mysql

  1. 客户端发送一条查询给服务器。
  2. 服务器先会查询缓存,若是命中了缓存,则当即返回存储在缓存中的结果。不然进入下一阶段。
  3. 服务务器端进行SQL解析、预处理,再由优化器生成对应的执行计划。
  4. MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询。
  5. 将结果返回给客户端。

查询缓存
在解析一个查询语句以前,若是查询缓存是打开的,那么MySQL会优先检查这个查询是否命中查询缓存中的数据,若是命中缓存直接从缓存中拿到结果并返回给客户端。这种状况下,查询不会被解析,不用生成执行计划,不会被执行。sql

语法解析和预处理器
MySQL经过关键字将SQL语句进行解析,并生成一棵对应的“解析树”。MySQL解析器将使用MySQL语法规则验证和解析查询。数据库

查询优化器
语法书被校验合法后由优化器转成查询计划,一条语句能够有不少种执行方式,最后返回相同的结果。优化器的做用就是找到这其中最好的执行计划。缓存

查询执行引擎
在解析和优化阶段,MySQL将生成查询对应的执行计划,MySQL的查询执行引擎则根据这个执行计划来完成整个查询。最常使用的也是比较最多的引擎是MyISAM引擎和InnoDB引擎。mysql5.5开始的默认存储引擎已经变动为innodb了。性能优化

下面简单的罗列几点差异:MyISAM类型不支持事务处理等高级处理,强调的是性能,InnoDB支持事物;MyISAM支持表级锁,InnoDB支持行级锁;MyISAM不支持外键,InnoDB支持外键(虽然不建议使用外键约束)。
查询当前默认的引擎能够经过SHOW VARIABLES LIKE '%storage_engine%'语句来查看。服务器

InnoDB事物和锁

InnoDB是支持事务的,那么到底什么是事务呢?
数据库事务(Database Transaction) ,是指做为单个逻辑工做单元执行的一系列操做,要么彻底地执行,要么彻底地不执行。并发

事物的基本特性(ACID)
原子性(atomicity):
事务中的操做要么都发生,要么都不发生。
一致性 (consistent):
在事务开始和完成时,数据必须保持一致状态(读一致、写一致)。
隔离性 (isolation):
隔离性指并发的事务是相互隔离的。一个事物操做不能对另外一个事物产生影响。
持久性(durable):
事务完成以后,它对于数据的修改是永久性的,即便出现系统故障也可以保持。性能

事务的隔离级别
若是不考虑事物的隔离级别会出现如下三种异常状况测试

  1. 不提交读(脏读)
    不提交读是指在一个事务处理过程当中读取了另外一个未提交的事务中的数据。
  2. 提交读(不可重复读)
    这种状况是一个事务范围内屡次查询却返回了不一样的数据值,这是因为在查询间隔,被另外一个事物修改并提交了。
  3. 幻读
    事物T1对一个表中全部的行的某个数据项作了从“1”修改成“2”的操做,这是T2又对这个表中插入了一行数据项,而这个数据项的数值仍是为“1”而且提交给数据库。而操做事务T1的用户若是再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉同样,这就是发生了幻读。

幻读和提交读都是读取了另外一条已经提交的事物(这一天与不提交读有区别),所不一样的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据总体。优化

Mysql数据库为咱们定义了四种隔离级别

  1. Serializable (串行化):它经过强制事物串行执行可避免不提交读、提交读、幻读的发生。
  2. Repeatable read (可重复读):可避免不提交读、提交读的发生。
    该级别在保证了在同一个事物中屡次读取一样的记录结果是一致的。
  3. Read committed (已提交读):可避免脏读的发生。
    一个事物开始时,只能看见已提交的事物所作得修改。这个级别会出现已提交读的现象。
  4. Read uncommitted (未提交读):最低级别,任何状况都没法保证。
    事物中的修改,即便没有提交,对其余事物都是可见的,这种隔离级别会出现脏读。在实际的应用中通常不多使用。

Repeatable read是mysql默认的隔离级别

InnoDB的锁分类及加锁方法
1、锁分类
InnoDB的锁定模式能够分为四类

  1. 共享锁(S)又叫读锁
    对同一行数据能够共享一把锁,没有得到锁的事务只能够读,不能够修改
  2. 排他锁(X)又叫写锁
    对同一行数据,得到该锁的事务可读可写,未得到锁的事务不可读也不可写.
  3. 意向共享锁(IS)
    事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁
  4. 意向排他锁(IX)
    事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

按照锁定级别划分为
1.表锁
开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的几率最高,并发度最低;适合于以查询为主,只有少许按索引条件更新数据的应用。
例如MyISAM引擎在执行查询语句的时候会自动给涉及的全部表加读锁,在执行更新操做(UPDATE、DELETE、INSERT)前,自动给涉及的表加写锁。
2.行锁
开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的几率最低,并发度也最高;适合有大量按索引条件并发更新状况。
InnoDB的行级锁定一样分为两种类型,共享锁和排他锁,而在锁定机制的实现过程当中为了让行级锁定和表级锁定共存,InnoDB也一样使用了意向锁(表级锁定)的概念,也就有了意向共享锁和意向排他锁这两种。
当一个事务须要给本身须要的某个资源加锁的时候,若是遇到一个共享锁正锁定着本身须要的资源的时候,本身能够再加一个共享锁,不过不能加排他锁。可是,若是遇到本身须要锁定的资源已经被一个排他锁占有以后,则只能等待该锁定释放资源以后本身才能获取锁定资源并添加本身的锁定。
3.页锁
开销和加锁时间界于表锁和行锁之间,咱们不多场景下会使用。
锁定级别的选择是由存储引擎来自行选择的。
4.间隙锁
当咱们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫作“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。
例如:select * from emp where empid > 100 for update;
是一个范围条件的检索,InnoDB不只会对符合条件的empid值为101的记录加锁,也会对empid大于101(这些记录并不存在)的“间隙”加锁。

二. 加锁方法
意向锁是InnoDB引擎自动加的,不须要用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁;事务能够经过如下语句显示给记录集加共享锁或排他锁。
共享锁:SELECT … LOCK IN SHARE MODE
排他锁:SELECT … FOR UPDATE
InnoDB行锁是经过给索引上的索引项加锁来实现的,只有经过索引条件检索数据,InnoDB才使用行级锁,不然,InnoDB将使用表锁。如下几种状况须要避免:

  1. 在不经过检索条件检索的时候,InnoDB确实使用的是表锁,而不是行锁。
  2. 因为MySQL的行锁是针对索引加的锁,不是针对记录加的锁,因此虽然是访问不一样行的记录,可是若是是使用相同的索引键,是会出现锁冲突的。
  3. 当表有多个索引的时候,不一样的事务可使用不一样的索引锁定不一样的行,另外,不管是使用主键索引、惟一索引或普通索引,InnoDB都会使用行锁来对数据加锁。
  4. 即使在条件中使用了索引字段,可是否使用索引来检索数据是由MySQL经过判断不一样执行计划的代价来决定的,若是MySQL认为全表扫描效率更高,好比对一些很小的表,它就不会使用索引,这种状况下InnoDB将使用表锁,而不是行锁。所以,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。

三.死锁
Mysql MyISAM老是一次得到所需的所有锁,要么所有知足,要么等待,所以不会出现死锁。但在InnoDB中,除单个SQL组成的事务外,锁是逐步得到的,当两个事务都须要得到对方持有的排他锁才能继续完成事务,这种循环锁等待就是典型的死锁。
一般来讲,死锁都是应用设计的问题,经过调整业务流程、数据库对象设计、事务大小,以及访问数据库的SQL语句,绝大部分死锁均可以免。
须要注意:

  1. 不一样的程序会并发存取多个表,应尽可能约定以相同的顺序来访问表,这样能够大大下降产生死锁的机会。
  2. 在程序以批量方式处理数据的时候,若是事先对数据排序,保证每一个线程按固定的顺序来处理记录,也能够大大下降出现死锁的可能。
  3. 在事务中,若是要更新记录,应该直接申请足够级别的锁,即排他锁,而不该先申请共享锁,更新时再申请排他锁,由于当用户申请排他锁时,其余事务可能又已经得到了相同记录的共享锁,从而形成锁冲突,甚至死锁。

SQL优化技巧

数据类型优化
更短的列一般更好,例如varchar(5)和varhchar(200)存储hello的空间开销是同样的,事实证实更长的列会消耗更多的内存,由于MySQL一般会分配固定大小的内存块来保存内部值。

使用缓存表和汇总表
以网站为例,假设须要计算以前24小时内发送的消息数,在一个繁忙的网站不可能维护一个实时精确的计算器,能够每一小时统计一次,而后把24个小时访问数叠加。

建立高性能的索引

1.使用独立的列
若是不是独立的列,则MySQL就不会使用索引
例如:
Select actor_id from actor where actor_id + 1 = 5;没法使用actor_id列的索引

2.联合索引
不少人对多列索引的理解不够,常见的错误是为每一个列建立独立的索引,或者按错误的顺序建立多列索引。当出现服务器对多个索引作相交操做时(一般是多个AND条件),一般意味着须要一个包含全部相关列的多列索引,而不是多个单独的单列索引。
当服务器须要多个索引作联合操做时(一般有多个OR条件),一般须要耗费大量的CPU和内存资源。一般有两种作法:
①使用union all
Select film_id,actor_id from film_actor where actor_id
Union all
Select film_id,actor_id from film_actor where film_id =1 and actor_id <>1;
②分别对film_id 和 actor_id建立索引。

3.选择合适的索引列顺序
Select * from payment where staff_id = 2 AND customer_id = 584
应该建立一个(staff_id,customer_id)索引仍是应该颠倒一下顺序?哪一个字段选择性更高,选择哪一个字段做为索引列的第一列。customer_id对应筛选掉的数据更多,则索引customer_id应该放在第一位。

4.冗余和重复索引
索引并非越多越好,过多的索引会对系统产生负担,一直未使用的索引应该把它删掉,而不是留在咱们的系统中。
例若有一张参保人表里面有100000行数据,每一个state_id值大概有20000条记录。在state_id列有一个索引对下面查询有用,查询名为Q1:
Select count(*) from table where state_id = 5;
一个简单的测试查询的执行速度每秒115次(QPS)。还有一个查询
Select state_id,city,address from userinfo where state_id = 5;
对于这个查询,测试结果QPS小于10。提高性能最简单的办法就是扩展索引为(state_id,city,address),索引扩展后Q2变快了,可是Q1变慢了,若是咱们想让两个查询都变得更快,就须要两个索引。多个索引的缺点是索引的成本更高。向表中插入100万数据,InnoDB引擎在一个索引状况下须要80秒,两个索引的状况下须要136秒。能够看出表中的索引越多插入速度越慢。

索引和锁

咱们在写查询语句的时候,要利用索引锁住最小范围内的行。
例如:
Select actor_id from sakila.actor where actor_id < 5
And actor_id <> 1 for update
这条语句仅仅返回 2-4,但实际上获取了1-4之间的行的排他锁,InnoDB 会锁住第1行,这是由于MySQL为该查询选择的执行计划是索引范围扫描。
就像这个例子显示的,即便使用了索引,InnoDB也可能锁住一些不须要的数据,若是不使用索引查找和锁定行的话问题可能会更糟,mysql会作全表扫描并锁住全部的行,而无论是否是须要。避免使用多个范围条件:
例如:select actor_id from actor where actor_id > 45
若是能改为select actor_id from actor where actor_id in (46,99)
这两种范围条件查询,MySQL没法再使用范围列后面的索引了,可是对于“多个等值条件查询”则没有这个限制。若是多个范围查询则索引就无能为力了。

查询性能优化

  1. 避免查询不须要的记录,须要多少查多少,建议使用limit
  2. 不要老是取出全部的列,不要轻易使用select * ,要判断是否是本身须要的列。
  3. 将以此大的工做量拆分红多个小的工做量
    例如一个大的事物,能够分拆成几个小事物执行,这样也能够将服务器本来一次性的压力分散到一个很长的时间段中,能够大大下降对服务器的影响,还能够减小。

执行计划

执行计划能够查看数据软件是如何扫描表,如何使用索引的,所以明白MySql语句的执行计划,对咱们优化mysql的性能很是有用。
查看执行计划可使用以下语句:
EXPLAIN SELECT * FROM inventory WHERE item_id = 16102176G; 这样的命令时,会给咱们反馈以下信息:
图片描述
Key: 列指出优化器选择使用的索引
Rows:用于分析结果集中的行数估计值,最好估值是1,通常来讲这种状况发生在当寻找的行在表中能够经过主键或者惟一键找到的时候。
Possible_keys:指出优化器为查询选定的索引
Key_len: 列定义了用于SQL语句的链接条件的键的长度。
Table:这个值多是个是代表、表的别名或者一个为查询产生临时表的标识符
Select_type:
提供了表示table列引用的使用方式的类型。最多见的值包括simple、primary,derived和union

  1. simple对于不包含子查询和其余复杂语句的简单查询
  2. primary 这是为复杂的查询而建立的最外层表
  3. derived当一个表不是物理表时叫作derived,派生表一个子查询
  4. dependent subquery 这个表明是子查询
  5. union 这是union语句其中的一个SQL元素

extra
extra列提供了有关不一样种类的MySQL 优化器路径的一系列
额外信息,下面给 出经常使用值的列表

  1. Using where
    这个值表示查询使用了where语句来处理结果
  2. Using temporary
    这个值表示使用了内部临时表
  3. Using index
    这个值重点强调了只须要使用索引就能够知足查询表的要求,不须要直接访问数据表数据。
  4. Distinct 这个值意味着MySql在找到第一个匹配的行以后就会定制搜索其余行。
相关文章
相关标签/搜索