zoukankan      html  css  js  c++  java
  • mysql库表优化实例

    一、SQL优化

    1、优化SQL一般步骤

    1.1 查看SQL执行频率

          SHOW STATUS LIKE 'Com_%';

          Com_select:执行SELECT操作的次数,一次查询累加1。其他类似

           以下参数只针对InnoDB存储引擎,累加算法略有不同

           Innodb_rows_read:SELECT查询操作插入的行数

           Innodb_rows_inserted/updated/deleted:执行INSERT/UPDATE/DELETE操作的行数

           通过以上参数,可以了解当前数据库应用是查询为主还是写入数据为主。

           对于事务型的应用。通过Com_commit和Com_rollback可以了解事务提交和回滚的情况,对于回滚操作非常的频繁的数据库,可能意味着应用编写存在问题。

           基本情况了解:

           Connections:试图连接MySQL服务器的次数。

           Uptime:服务器工作时间 

           Slow_queries:慢查询的次数

    1.2 定位执行效率比较低的SQL语句

           - 通过慢查询日志定位慢SQL,用--log-slow-queries[=file_name]选项启动时,mysqld会写一个所有执行时间超过long_query_time秒的SQL语句的日志文件。

           - 使用SHOW FULL PROCESSLIST; 查看当前MySQL在进行的线程,同时对一些锁表操作进行优化。      

    1.3 通过EXPLAIN分析慢SQL

          语法:EXPLAIN SQL语句

          结果:

          

          - select_type:表示SELECT的类型,常见的取值有SIMPLE(简单表,即不使用表连接或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION中的第二个或者后面的查询语句)、SUBQUERY(子查询中的第一个SELECT)等。

          - table:输出结果的表名

          - type:表示MySQL在表中找到所需行的方式,或者叫访问类型

            常见的有:ALL index range ref eq_ref const,system NULL,从左到右,性能由最差到最好。

            type=ALL:全表扫描。

            type=index:索引全扫描,MySQL遍历整个索引来查询。

            type=range:索引范围扫描,常见于<、<=、>、 >=、 between。

            type=ref:使用非唯一索引扫描或唯一索引的前缀扫描,返回匹配某个单独值的记录。

            type=eq_ref:类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者unique index作为关联条件。

            type=const/system:单表中最多有一个匹配行,查询起来非常迅速,一般主键primary key或者唯一索引unique index进行的查询,通过唯一索引uk_email访问的时候,类型type为const;而从我们构造的仅有一条记录的a表中检索时,类型type为system。

            type=NULL:MySQL不用访问表或者索引,就能直接得到结果。

            类型type还有其他值,如ref_or_null(与ref类似,区别在于条件中包含对NULL的查询)、index_merge(索引合并优化)、unique_subquery(in的后面是一个查询主键字段的子查询)、index_subquery(与unique_subquery类似,区别在于in的后面是查询非唯一索引字段的子查询)

           - possible_keys:表示查询时可能使用的索引。

           - key:表示实际使用的索引。

           - key_len:使用到索引字段的长度。

           - rows:扫描行的数量

           - Extra:执行情况的说明和描述,包含不适合在其他列中显示但是对执行计划非常重要的额外信息。

             Using where:表示优化器除了利用索引来加速访问之外,还需要根据索引回表查询数据。

    1.4 通过show profile分析SQL

          查看当前MySQL是否支持profile

          

          默认profiling是关闭的,可以通过set语句在Session级别开启profiling:set profiling=1;

          使用方法:

          - 执行统计查询:

          

          - 查找上述SQL的query ID:

          

          - 查找上述SQL执行过程中每个线程的状态和消耗时间:

          

          Sending data状态表示MySQL线程开始访问数据并行把结果返回给客户端,而不仅仅是返回结果给客户端。由于在Sending data状态下,MySQL线程往往需要做大量的磁盘读取操作,所以经常是整个查询中耗时最长的状态。

           - 查看详细信息并排序:

    复制代码
    SELECT
        STATE,
        SUM(DURATION) AS TR,
        ROUND(
            100 * SUM(DURATION) / (
                SELECT
                    SUM(DURATION)
                FROM
                    information_schema.PROFILING
                WHERE
                    QUERY_ID = 3
            ),
            2
        ) AS PR,
        COUNT(*) AS Calls,
        SUM(DURATION) / COUNT(*) AS "R/Call"
    FROM
        information_schema.PROFILING
    WHERE
        QUERY_ID = 3
    GROUP BY
        STATE
    ORDER BY
        TR DESC;
    复制代码

          

          进一步获取all、cpu、block io、context switch、page faults等明细类型来查看MySQL在使用什么资源上耗费了过高的时间,例如,选择查看CPU的耗费时间。

          此时可获取到sending data时间主要消耗在CPU上

          

          提示:InnoDB引擎count(*)没有MyISAM执行速度快,就是因为InnoDB引擎经历了Sending data状态,存在访问数据的过程,而MyISAM引擎的表在executing之后直接就结束查询,完全不需要访问数据。

    2、索引问题

          索引是数据库优化中最常用也是最重要的手段之一,通过索引通常可以帮助用户解决大多数的SQL性能问题。

    2.1 存储引擎的分类

          - B-Tree索引:最常见的索引类型,大部分引擎都支持B树索引。

          - HASH索引:只有Memory引擎支持。

          - R-Tree索引:空间索引是MyISAM的一个特殊索引类型,主要用于地理空间数据类型。

          - Full-text:全文索引是MyISAM的一个特殊索引类型,主要用于全文索引,InnoDB从MySQL5.6版本开始对其支持。

          MySQL目前不支持函数索引,但是能对列的前面某一部分进行索引,例如标题title字段,可以只取title的前10个字符进行索引,但是在排序Order By和分组Group By操作的时候无法使用。前缀索引创建例子:create index idx_title on film(title(10))。

          常用的索引是B-Tree和Hash。Hash只有Memory/Heap引擎支持。适用于Key-Value查询,通过Hash比B-Tree更迅速。Hash索引不使用范围查询。Memory/Heap引擎只有在=条件下才会使用索引。

    2.2 MySQL如何使用索引

          创建一个复合索引:ALTER TABLE rental ADD INDEX idx_rental_date (rental_date, inventory_id, customer_id);

    2.2.1 MySQL中能够使用索引的典型场景

          - 匹配全值,对索引中所有列都指定具体值,即是对索引中的所有列都有等值匹配条件。

            比如上述创建的idx_rental_date,包含rental_date, inventory_id, customer_id,此时如果where子句中包含三者,即为全值匹配。

            

            字段key为idx_rental_date,表示优化器使用的是索引idx_rental_date进行扫描。

          - 匹配值的范围查询,对索引的值能够进行范围查找。

            

            类型type为range说明优化器选择范围查询,索引key为idx_fk_customer_id说明优化器选择索引idx_fk_customer_id来加速访问。

          - 匹配最左前缀,意思是在复合索引中,索引是从左边第一个开始查找,不会跨过第一个从第二个查找,比如一个联合索引包含(c1, c2, c3)三个字段,可是不能被c2或者c2+c3等值查询利用到。

            添加索引:ALTER TABLE payment ADD INDEX idx_payment_date(payment_date, amount, last_update);此时第一个字段是payment_date

            如果查询条件包含索引的第一列支付日期,能够使用复合索引idx_payment_date进行过滤。

            比如:

            

            如果使用的是第二个支付金额不包含第一个,则不会使用索引。

            比如:(此时key为空)

            

          - 仅仅对索引进行查询,意思是查询的数据都在索引字段中时,查询的效率更高。

            比如此时查询last_update且last_update字段被包含在索引字段中如图:

            

            那么直接访问索引就可以获取所需的数据,不需要通过索引回表,此时的Extra也变成了Using index,Using index指的是覆盖索引扫描。

            查询结果:

            

          - 匹配列前缀,仅仅使用索引中的第一列,并且只包含索引第一列的开头一部分进行查找。

            例如查找标题title是以AFRICAN开头的电影信息。

            首先创建索引:CREATE INDEX idx_title_desc_part ON film_text (title(10), description(20));

            查询可以看到idx_title_desc_part 被使用,Using where表示优化器需要通过索引回表查询数据:

            

          - 匹配部分精确,其他部分范围匹配。

            指定日期,不同客户编号

            

            类型type为range说明优化器选择范围查询,索引key为idx_rental_date说明优化器选择索引idx_rental_date帮助加速查询,同时所查询的字段在索引中,索引Extra能看见Using index。

          - 列名 is null,此种情况下会使用索引。

            例如:

            

     2.2.2 存在索引但不能使用的典型场景

          - 以%开头的LIKE查询不能利用B-Tree索引。

            如下:

            

            B-Tree索引结构,以%开头的查询无法利用索引,一般可使用全文索引(Fulltext)来解决类似问题。或者使用InnoDB表上的二级索引,首先获取满足条件的列表的id,之后再根据主键回表去检索记录。

            

          - 数据类型出现隐式转换不会使用索引,有些列类型是字符串,在写where条件时,需要将常量值用引号括起来。

          - 复合索引,查询条件需要包括最左边部分,否则不会使用复合索引。即leftmost

          - MySQL执行语句时会有优化器选择的过程,当全表扫描的代价小于索引的代价时,会使用全表扫描,所以此时需要更换一个筛选性更高的条件。

          - 用or分开的条件,如果or前的列有索引,后面的没有索引,则不会使用索引。

    2.3 查看索引的使用情况

          

          如果索引正在工作,Handler_read_key的值将很高,这个值代表了一个行被索引值读的次数,如果很低,说明增加索引得到的性能改善不高,因为索引并没有被经常使用。

          Handler_read_rnd_next的值高则意味着查询运行低效,并且应该建立索引补救。这个值的含义是在数据文件中读下一行的请求数。如果值比较大,说明正在进行大量的表扫描,则通常说明表索引不正确或写入的查询没有利用索引。

    3、常用SQL优化

    3.1 大批量插入数据(load)

          - MyISAM

            - 打开或者关闭MyISAM表非唯一索引的更新,可以提高导入效率(导入数据到非空MyISAM表)。

              步骤:ALTER TABLE tab_name DISABLE KEYS; 导入数据; ALTER TABLE tab_name ENABLE KEYS;

              导入数据到一个空的MyISAM表,默认是先导入数据然后才创建索引的,所以不用设置。

          - InnoDB

            - 因为InnoDB类型的表是按照主键顺序保存的,所以降导入的数据按主键的顺序排列,可以有效的提高导入数据的效率。

            - 关闭唯一性校验,SET UNIQUE_CHECKS = 0,导入结束后开启。

            - 如果使用的是自动提交的方式,在导入前使用SET AUTOCOMMIT = 0,导入结束后在恢复。 

    3.2 优化INSERT语句

            - 同一客户端插入很多行,应尽量使用多个值的INSERT语句。比如:INSERT INTO tab_name values(),(),()...

            - 不同客户端插入很多行,可以使用INSERT DELAYED,DELAYED含义是让INSERT语句放置到内存的队列中,并没有写入磁盘。LOW_PRIORITY是在所有其他用户对表的读写完成后才进行插入。

            - 将索引文件和数据文件分别放置在不同的磁盘上。

            - MyISAM如果进行批量插入,增加bulk_insert_buffer_size的值。

            - 从文件装载一个表时,使用LOAD DATA INFILE,比INSERT语句快20倍。

    3.3 优化ORDER BY 语句

    3.3.1 MySQL排序方式

          - 通过有序索引顺序扫描直接返回有序数据。

            在表customer上有索引idx_fk_store_id,指向字段store_id

            

            此时order by使用store_id排序时,Extra为Using index,不需要额外的排序,操作效率较高。

            

          - 通过Filesort排序,所有不是通过索引直接返回排序结果的排序都叫Filesort排序。MySQL服务器对排序参数的设置和需要排序数据的大小决定排序操作是否使用磁盘文件或临时表。

            Filesort是通过算法,将取得的数据在sort_buffer_size系统变量设置的内存排序区中进行排序,如果内存装不下,就会将磁盘上的数据进行分块,再对各个数据块进行排序,然后合并。sort_buffer_size的排序区为线程独占,可能同时存在多个。

            比如通过store_id排序所有客户记录时,此时为全表扫描,并且使用filesort。

            

            一般优化方式:减少额外的排序,通过索引直接返回有序数据。尽量使WHERE条件和ORDER BY使用相同的索引,并且ORDER BY的顺序和索引数据相同,并且ORDER BY的字段都是升序或者都是降序,否则肯定会出现Filesort。

            - 不会使用索引情况:

              - order by的字段混合ASC和DESC:SELECT * FROM TAB_NAME ORDER BY KEY_PART1 DESC, KEY_PART2 ASC;

              - 用于查询的关键字与ORDER BY 中所使用的不相同:SELECT * FROM TAB_NAME WHERE KEY2=CONSTANT ORDER BY KEY1;

              - 对不同的关键字使用ORDER BY:SELECT * FROM TAB_NAME ORDER BY KEY1, KEY2;

    3.3.2 优化Filesort

          Filesort有两种排序算法:

          - 两次扫描算法:首先根据条件取出排序字段和行指针信息,之后在排序区sort buffer中排序。如果排序区sort buffer不够,则在临时表Temporary Table中存储排序结果,完成排序后根据行指针回表读取记录。需要两次访问数据,第一次获取排序字段和行指针信息,第二次根据行指针获取记录,第二次读取操作可能导致大量随机I/O操作,优点是排序的时候内存开销较少。

          - 一次扫描算法:一次性取出满足条件的行的所有字段,然后在排序区sort buffer中排序后直接输出结果集,排序的时候内存开销比较大,但是排序效率比两次扫描要高。

          MySQL通过比较系统变量max_length_for_sort_data的大小和Query语句取出的字段总大小来判断使用哪种算法。max_length_for_sort_data大使用第二种算法,否则第一种。

          适当加大系统变量max_length_for_sort_data的值,能够让MySQL选择更优化的Filesort排序算法。但是过大会引起CPU利用率过低和磁盘I/O过高。

          适当加大sort_buffer_size排序区,尽量让排序在内存中完成,而不是通过创建临时表放在文件中进行;该大小需要考虑数据库活动连接数和服务器内存的大小来适当设置排序区。因为这个参数是每个线程独占的,如果设置过大,会导致服务器SWAP严重。尽量只使用必要的字段,而不是SELECT *。

     3.3.3 优化GROUP BY

          默认情况下,MySQL对所有的GROUP BY字段进行排序,如果查询包括GROUP BY但是用户想要避免排序结果的消耗,则可以指定ORDER BY NULL禁止排序。

          SELECT XXX FROM XXX GROUP BY XXX ORDER BY NULL

    3.3.4 优化嵌套查询

          使用子查询可以一次性的完成很多逻辑上需要多个步骤才能完成的SQL操作,同时也可以避免事务或者表锁死。子查询可以被更有效的连接JOIN替代。

    3.3.5 优化OR条件

          对于含有OR的查询子句,如果要利用索引,则OR之间的每个条件列都必须要用到索引。

    3.3.6 优化分页查询

           一般分页查询时,通过创建覆盖索引能够比较好地提高性能,但是当分页为1000 20时,此时会排序前1020条记录后返回1001到1020条记录,前1000条记录都会被抛弃,查询和排序的代价非常高。

           - 第一种优化思路:从索引完成排序分页的操作,最后根据主键关联回原表查询所需的其他列内容。

             例如:对电影表film根据标题title排序后取某一页数据

             - 直接查询

               

               按照索引分页后回表方式改写SQL

               

          - 第二种优化思路:把limit查询转换成某个位置的查询。

            假设需要查询第100页,则可以记录99页最后一行的id(倒序或者正序),然后再次查询时使用where取99也最后一行的id进行大于或者小于,然后在直接使用limit n即可。n为每页显示的行数。

            比如以每页十行查询第100页的数据,可以使用一下步骤:

            首先查询到第99行最后一行的id:

            

            在通过获取到的id取小于它的值,取10行,即为第100页:

            

            与直接查询结果相比较:

            

            explain比较:

            

            

    3.3.7 使用SQL提示

            - USE INDEX

              提示MySQL参考使用的索引,可以让MySQL不再考虑其他可用的索引。

              比如:select count(1) from tab_name use index(index_name) where xxx; 此时查询会用index_name所以,而忽略其他。

            - IGNORE INDEX

              提示MySQL忽略一个或者索引。

              比如:select count(1) from tab_name ignore index (index_name); 此时查询会忽略index_name索引。

            - FORCE INDEX

               强制MySQL使用某个索引,使用情况:当where子句取id>1的值,因为数据库中大部分库表都是大于1的,所以会全盘扫描,此时使用use index不可用,所以使用force index。

               比如:select * from tab_name force index(index_name) where id > 1;

    4、常用SQL技巧

    4.1 正则表达式的使用

          

          -  ^ 在字符串的开始处进行匹配

             匹配是否已a开头

             

          -  $ 在字符串的末尾处进行匹配。

          -  . 匹配任意单个字符,包括换行符。

             

          -  [...] 匹配出括号内的任意字符。

             

          -  [^...] 不匹配[]内的任意字符

          真实例子:

          

          使用like格式如下:SELECT first_name, email FROM customer WHERE email LIKE "%@163.com" OR email LIKE "%@163,com";

     4.2 利用RAND()提取随机行

          随机抽取n条数据:SELECT * FROM tab_name ORDER BY RAND() LIMIT n;

    4.3 GROUP BY的WITH ROLLUP

          WITH ROLLUP可以检索出更多的分组聚合信息。

          比如查询经手员工每日的支付金额的统计。不使用WITH ROLLUP如下:

          

            加入 WITH ROLLUP如下:

            

            WITH ROLLUP反映的是一种OLAP思想,可以满足用户想要得到的任何一个分组以及分组组合的聚合信息值。上个例子中,WITH ROLLUP帮用户统计了每日的总金额和所有的总金额。注意:ROLLUP不能和ORDER BY使用,且limit在ROLLUP后面。

    4.4 数据库名、表名大小写问题

          由于Windows、Mac OS、Unix对库表名已经查询使用的大小写敏感不一致,所以最好将库表进行规范保存,且查询语句也规范使用。

    ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    二、优化数据库对象

    1、优化表的数据类型

          应用设计的时候需要考虑字段的长度留有一定的冗余,但不推荐很多字段都留有大量的冗余,这样既浪费磁盘空间,也在应用操作时浪费物理内存。

          在MySQL中,可以使用函数PROCEDURE ANALYSE()对表进行分析,给出优化建议。(16,  256)是指不为包含的值多于16或者256字节的ENUM类型提出建议。

          

          Optimal_fieldtype为优化建议,可以通过alter修改字段类型:ALTER TABLE TAB_NAME MODIFY COLUMN Optimal_fieldtype_VALUE;

    2、逆规范化

          反规划的好处是降低连接操作的需求、降低外码和索引的数目、减少表的数目,带来的问题可能会出现数据的完整性问题,虽然查询加快,但是会降低修改速度。在进行反规范操作之前,要充分考虑数据的存取需求、常用表的大小、一些特殊的计算、数据的物理存储位置。常用的反规范技术如下:

          - 增加冗余列:指在多个表中具有相同的列,它常用来查询时避免连接操作。

          - 增加派生列:指增加的列来自其他表中的数据,由其他表中的数据经过计算生成,作用是减少连接操作,避免使用集函数。

          - 重新组表:指如果许多用户需要查看两个表连接出来的结果数据,则把这两个表重新组成一个表来减少连接而提高性能。

          - 分库分表:https://www.cnblogs.com/dukuan/p/9480610.html

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

          对于数据量较大的表,在其上进行统计查询通常会效率很低,并且还会对线上应用产生负面影响。此种情况下可以使用中间表提高统计查询的效率。

          一般步骤:创建表结构和原表结构相同的表,迁移数据需要统计的数据,进行统计。

          中间表在统计查询中的优点:

              - 中间表复制源表数据,并且与源表相“隔离”,在中间表上做统计查询不会对线上应用产生负面影响。

              - 中间表上可以灵活的添加索引或增加临时用的新字段,从而达到提高统计查询效率和辅助统计查询作用。 

    三、锁问题

    1、锁概述

          MyISAM和MEMORY存储引擎采用表级锁,BDB存储引擎(MySQL5.1后不直接支持此存储引擎)采用的页面锁,也支持表级锁,InnoDB支持行级锁和表级锁,默认是行级。

          - 表级锁:开销小,加锁快,不会出现死锁,锁定粒度大,发生锁冲突的概率最高,并发度最低。

          - 行级锁:开销大,加锁慢,会出现死锁,锁定粒度小,发生冲突的概率最低,并发度也最高。

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

          表级锁更适合以查询为主,只有少量按索引条件更新数据的应用,如Web应用。而行级锁更适合有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如在线事物处理系统。

    2、MyISAM表锁

          MyISAM只支持表锁。

    2.1 查询表级锁争用情况

          

          Table_locks_immediate:产生表级锁定的次数。

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

    2.2 MySQL表级锁的锁模式

          表级锁分为表共享读锁和表独占写锁。兼容性如下:

          

          对于MyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一个表的写请求。对表的写操作,会阻塞同一表的读和写。MyISAM读操作和写操作以及写操作之间是串行的。

    2.3 如何加锁表

          MyISAM在执行SELECT、UPDATE、DELETE、INSERT前,会自动给涉及的表加锁,无需用户干预。

          显式加锁情况:比如同时要查询或者比对两个表中的内容,为防止在查其中一个时另一个有更新或者新数据,此时需要显式的为两个表加锁。

          加锁命令:LOCK TABLES tab_name READ LOCAL, tab_name2 READ LOCAL;  -- LOCAL表示允许其他用户在MyISAM表尾并发插入记录。使用显式加锁时,必须同时取得所有涉及表的锁,而且加锁后只能访问加锁的这些表,不能访问其他表。并且如果加的是读锁,那么只能执行查询操作。并且加锁时需要对别名也要加锁。

    2.4 MyISAM并发插入

          MyISAM通过concurrent_insert参数决定是否允许并发插入

          - 0:不允许并发插入

          - 1:MyISAM表中无空洞,允许读的同时在表末尾插入记录。默认设置

          - 2:无论是否有无空洞,都能插入。

          可以利用并发插入特性来解决应用中对同一表查询和插入的锁争用。同时定期在系统空闲时整理空间碎片,收回因删除记录而产生的中间空洞。

          注意:只能insert不能update和delete。且锁表的session不能获取到新插入到的数据。

    2.5 MyISAM的锁调度

          同一时刻请求的写锁和读锁,MySQL会优先处理写进程。即使是读请求先到等待队列,写锁也会插入到读锁请求之前,这也是MyISAM表不太适合于有大量更新操作和查询操作应用的原因。

          调节MyISAM的调度行为:

          - 通过制定启动参数low-priority-updates, 使MyISAM引擎默认给予读请求以优先的权利。

          - 通过执行命令 SET LOW_PRIORITY_UPDATES = 1,来降低更新请求的优先级。

          - 通过制定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,降低该语句的优先级。

          MySQL也提供了一种折中的办法,即给系统参数max_write_lock_count设置一个合适的值,当一个表的读锁达到这个值后,MySQL就暂时将写请求的优先级降低。

    3、InnoDB锁问题

    3.1 背景知识

    3.1.1 事务及其ACID属性

          事务是由一组SQL语句组成的逻辑处理单元,具有以下4个属性,通常简称为事务的ACID属性。

          - 原子性:事务是一个原子操作单元,其对数据的修改,要么全部执行,要么全都不执行。

          - 一致性:在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构也都必须是正确的。

          - 隔离性:数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的独立环境执行。

          - 持久性:事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。

    3.1.2 并发事务处理的问题

          相对于串行处理来说,并发事务处理能大大增加数据库资源利用率,提供吞吐量,但会引起下列问题:

          - 更新丢失:两个或多个事务同时操作同一行,会覆盖其他事务的更新。

          - 脏读:当一个事务在对一条记录做修改未完成并提交前,另一个事务来读取同一条记录,这时读取到的数据叫脏读。

          - 不可重复读:一个事务在读取某些数据后的某个时间,再次读取,发现读出的数据已经发生了改变或某些记录已经被删除了。

          - 幻读:一个事务按相同的查询条件重新读取以前检索过的数据,其他事务插入了满足其条件的新数据。

    3.1.3 事务隔离级别

          线上业务应完全避免更新丢失,但是避免此情况需要应用程序对要更新的数据加必要的锁来解决。但是关于读一致性,必须由数据库提供一定的事务隔离机制来解决。数据库事务隔离的方式,基本上可分为以下两种:

          - 在读取数据之前,对其加锁。 

          - 使用数据多版本并发控制(MVCC/MCC),按照请求时间点创建快照。

          数据库的事务隔离越严格,并发副作用越小,相应的代价也就越大,因为事务隔离实质是进行"串行化"。

          为了解决隔离和并发的矛盾,ISO/ANSI SQL92定义了4个事务隔离级别。应用可以根据自己的业务逻辑要求,选择不同的隔离级别来平衡隔离和并发的矛盾。

          

    3.2 获取InnoDB行锁争用情况

          

          如果Innodb_row_lock_waits和Innodb_row_lock_time_avg的值比较高,则争用比较严重。

          此时可以通过查询information_schema数据库中的表来查看锁情况,或者通过设置InnoDB Monitors来观察发生锁冲突的表、数据行等。

          - 通过information_schema

            SELECT * FROM innodb_locks;

            SELECT * FROM innodb_lock_waits;

          - 通过InnoDB Monitors

            CREATE TABLE innodb_monitor(a INT) ENGINE = INNODB;

            然后通过:SHOW ENGINE INNODB STATUS查看

            关闭监视器:DROP TABLE innodb_monitor;

    3.3 InnoDB行锁模式及加锁方法

          InnoDB实现了两种类型的行锁:

          - 共享锁:允许另一个事务也获得共享锁,但是阻止其他事务获得相同数据集的排他锁。

          - 排他锁:允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。

          事务获取锁的方式:

          - 共享锁:SELECT ... WHERE ... LOCK IN SHARE MODE;

          - 排他锁:SELECT ... WHERE ... FOR UPDATE;

          对于锁定行后需要进行更新操作的应用,用过使用排他锁。

    3.4 InnoDB行锁实现方式

          InnoDB行锁是通过索引上的索引项加锁来实现的,如果没有索引,InnoDB将通过隐藏的聚簇索引来记录加锁。分为3种情形:

          - Record lock:对索引项加锁。

          - Gap lock:对索引项之间的“间隙”、第一条记录前的“间隙”或最后一条记录后的“间隙”加锁。

          - Next-key lock:前两种的组合,对记录及其前面的间隙加锁。

          注意:如果不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,等同于表锁,生产环境中需要注意这一特性防止导致大量的锁冲突,从而影响并发性能。

          由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果使用相同的索引建,会出现锁冲突。

          当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,但是当不同的session查询相同的数据时,同样会阻塞。

          当表的数据较少,此时MySQL可能会全盘扫描,此时会导致不使用索引查询,进而导致全表加锁。

          MySQL检索的数据类型与索引字段不同,会进行数据转换,但却不会使用索引,所以会导致InnoDB对所有的记录加锁。

    3.5 Next-Key锁

          当我们使用范围查询,并请求共享或排他锁时,InnoDB会给符合条件的所有索引项加锁,对于在范围内但是不存在的记录,叫做间隙GAP,同时也会被加锁,这个锁叫做Next-Key锁。Next-Key锁时为了防止幻读。

          当使用范围检索并锁定记录时,InnoDB会阻塞条件范围内键值的并发插入,这往往会造成严重的锁等待。因此,在实际应用开发中,尤其是并发插入比较多的应用,尽量使用相等条件来访问更新数据,避免使用范围条件。

     3.6 什么时候使用行级锁

          对于InnoDB,绝大部分情况下都应该使用行级锁,个别特殊事务中,可以考虑使用表级锁。

          - 事务需要更新大部分或全部数据,表比较大,如果使用默认的行锁,会造成事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突。这种情况考虑使用表锁来提高事务的执行速度。

          - 事务设计多个表,比较复杂,很可能引起死锁,造成大量事务回滚,此时可考虑使用表锁避免死锁,减少数据库因事务回滚带来的开销。

          当然,生产环境中最好不要出现这两种事务,否则就应该考虑MyISAM了。

          表锁注意事项:

          - 表锁不是由InnoDB存储引擎管理的,而是由MySQL Server负责的,仅当autocommit=0,innodb_table_locas=1(缺省值)时,InnoDB才能知道MySQL加的表锁,也才能感知InnoDB加的行锁, 这种情况下,InnoDB才能自动识别涉及表级锁的死锁,否则InnoDB将无法自动检测并处理这种死锁。

          - 在用LOCK TABLES对InnoDB加锁时,需要将AUTOCOMMIT设为0,否则不会给表加锁;事务结束前,不要用UNLOCKS TABLES释放表锁,因为UNLOCK TABLES会隐含提交事务;COMMIT或ROLLBACK并不能释放用LOCK TABLES加的表级锁,必须用UNLOCK TABLES释放表锁。

            方式如下:写表t1并从表t2读

            SET AUTOCOMMIT = 0;

            LOCK TABLES t1 WRITE, t2 READ;

            [do something..] 

            COMMIT;

            UNLOCK TABLES;

    3.7 关于死锁

          MyISAM表锁是一次获得所需全部锁,要么全部满足,要么等待,因为不会出现死锁。但InnoDB锁是逐步获得的,所以InnoDB会发生死锁的可能。

          比如:session1正在对table_1进行 select for update(获得tb1排他锁) ,此时session2对table_2进行select for update(获得tb2排他锁),如果此时session1对tb2进行select for update,会出现等待,直到session2释放,但是如果session2没有释放并且又请求tb1进行select for update,那么此时会出现死锁。

          发生死锁后,InnoDB一般都能自动检测到,然后释放一个事务锁并回退,另一个事务获得锁继续完成事务。但在涉及外部锁或涉及锁的情况下,InnoDB并不能完全自动检测到死锁,这需要通过设置锁超时参数innodb_lock_wait_timeout来解决,但这个参数并不是用来解决死锁问题,在并发访问比较高的情况下,如果大量事务因无法立即获得所需的锁而挂起,会拖垮数据库,设置合适的值可以避免或者减少此种情况的发生。

          通常来说,死锁都是应用设计的问题,通过调整业务流程、数据库对象设计、事务大小,以及访问数据库的SQL语句,绝大部分死锁都可以避免。

          避免死锁的常用方法:

          - 程序并发存取多个表,尽量约定相同的顺序来访问量。        

          - 程序批量处理数据的时候,事先对数据排序,保证每个线程按固定的顺序来处理记录。

          - 在事务中,如果要更新记录,应该直接申请足够级别的排他锁,而不是先申请共享锁,后申请排他锁。要不然其他事务可能已经获得了相同的共享锁,从而造成锁冲突。

          - 在REPEATABLE-READ隔离级别下,如果两个线程同时对相同条件记录用SELECT FOR UPDATE加排他锁,在没有符合条件的记录的情况下,两个线程都会加锁成功。因为两个线程查询均无此记录,便会尝试插入一条新纪录,如果两个线程都这么做,就会出现死锁。这种情况将隔离级别改成READ COMMITTED就可避免。

    ---------

    原文地址:https://www.cnblogs.com/dukuan/p/9517365.html

  • 相关阅读:
    springmvc的单文件上传
    使用Eclipse创建maven项目
    @responseBody注解的使用
    Oracle-怎么在表的特定位置增加列
    Oracle-创建新表,创建备份表,对表中插入多条数据
    EXCEL-排名前三名显示小红旗,后三名显示小黑旗
    Hive-insert into table 与 insert overwrite table 区别
    数仓工具介绍
    Hive-删除表(drop、truncate的区别)
    EXCEL-批量修改列宽
  • 原文地址:https://www.cnblogs.com/alliswell2king/p/11757105.html
Copyright © 2011-2022 走看看