MySQL高级——查询优化、慢查询日志、锁机制、主从赋值

MySQL高级——查询优化、慢查询日志、锁机制、主从赋值

  • 一、查询截取分析
    • 1.1 查询优化
      • 1.1.1 小表驱动大表
      • 1.1.2 ORDER BY优化
      • 1.1.3 GORUP BY优化
    • 1.2 慢查询日志
      • 1.2.1 慢查询基本介绍
      • 1.2.2 慢查询日志的使用
      • 1.2.3 日志分析工具
    • 1.3 批量数据脚本
    • 1.4 Show Profile
    • 1.5 全局查询日志
  • 二、MySQL锁机制
    • 2.1 锁的分类
    • 2.2 表锁(偏读)
      • 2.2.1 读锁案例
      • 2.2.2 写锁案例
      • 2.2.3 案例结论
      • 2.2.4 表锁分析
    • 2.3 行锁(偏写)
      • 2.3.1 行锁案例
      • 2.3.2 案例结论
      • 2.3.3 行锁分析
      • 2.3.4 优化建议
    • 2.4 页锁(了解)
  • 三、主从复制
    • 3.1 复制基本原理
    • 3.2 一主一从配置实战(不全)

一、查询截取分析

分析:
1、观察,至少跑1天,看看生产的慢SQL情况。
2、开启慢查询日志,设置阈值,比如超过5秒钟的就是慢SQL,并将它抓取出来。
3、explain + 慢SQL分析。
4、show Profile。
5、运维经理 OR DBA,进行MySQL数据库服务器的参数调优。
总结(大纲):
1、慢查询的开启并捕获。
2、explain + 慢SQL分析。
3、show Profile查询SQL在MySQL数据库中的执行细节和生命周期情况。
4、MySQL数据库服务器的参数调优。

1.1 查询优化

1.1.1 小表驱动大表

优化原则:对于MySQL数据库而言,永远都是小表驱动大表。

MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第1张图片
总结:括号里的结果集小的时候用in,反之用exists。(in返回的是数据,而exists返回true或false。)

EXISTS:

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

提示:

  • EXISTS(subquery)子查询只返回true或者false,因此子查询中的SELECT *可以是SELECT 1 OR SELECT X,它们并没有区别。
  • EXISTS(subquery)子查询的实际执行过程可能经过了优化而不是我们理解上的逐条对比,如果担心效率问题,可进行实际检验以确定是否有效率问题。
  • EXISTS(subquery)子查询往往也可以用条件表达式,其他子查询或者JOIN替代,何种最优需要具体问题具体分析。

1.1.2 ORDER BY优化

  • ORDER BY子句,尽量使用索引排序,避免使用Using filesort排序。

数据准备

CREATE TABLE `talA`(
`age` INT,
`birth` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO `talA`(`age`) VALUES(18);
INSERT INTO `talA`(`age`) VALUES(19);
INSERT INTO `talA`(`age`) VALUES(20);
INSERT INTO `talA`(`age`) VALUES(21);
INSERT INTO `talA`(`age`) VALUES(22);
INSERT INTO `talA`(`age`) VALUES(23);
INSERT INTO `talA`(`age`) VALUES(24);
INSERT INTO `talA`(`age`) VALUES(25);
/* 创建索引 */
CREATE INDEX idx_talA_age_birth ON `talA`(`age`, `birth`);

案例:

MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第2张图片
MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第3张图片

总结:

MySQL支持两种方式的排序,FileSortIndexIndex的效率高,它指MySQL扫描索引本身完成排序。FileSort方式效率较低。

ORDER BY满足两情况,会使用Index方式排序:

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

结论:尽可能在索引列上完成排序操作,遵照索引建的最佳左前缀原则。

  • 如果不在索引列上,File Sort有两种算法:MySQL就要启动双路排序算法和单路排序算法

1、双路排序算法:MySQL4.1之前使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,读取行指针和ORDER BY列,対他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出。一句话,从磁盘取排序字段,在buffer中进行排序,再从磁盘取其他字段。
取一批数据,要对磁盘进行两次扫描,众所周知,IO是很耗时的,所以在MySQL4.1之后,出现了改进的算法,就是单路排序算法。

2、单路排序算法:从磁盘读取查询需要的所有列,按照ORDER BY列在buffer対它们进行排序,然后扫描排序后的列表进行输出,它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间,因为它把每一行都保存在内存中了。
存在的问题:在 sort_buffer 中,方法 B 比方法 A 要多占用很多空间,因为方法 B 是把所有字段都取出, 所以有可能取出的数据的总大小超出了 sort_buffer 的容量,导致每次只能取 sort_buffer 容量大小的数据,进行排序(创建 tmp 文件,多 路合并),排完再取取 sort_buffer 容量大小,再排……从而多次 I/O。也就是本来想省一次 I/O 操作,反而导致了大量的 I/O 操作,反而得不偿失

单路复用算法的优化策略:

  • 增大sort_buffer_size参数的设置。
  • 增大max_length_for_sort_data参数的设置。
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第4张图片

总结:
MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第5张图片

1.1.3 GORUP BY优化

  • GROUP BY实质是先排序后进行分组,遵照索引建的最佳左前缀。
  • 当无法使用索引列时,会使用Using filesort进行排序,增大max_length_for_sort_data参数的设置和增大sort_buffer_size参数的设置,会提高性能。
  • WHERE执行顺序高于HAVING,能写在WHERE限定条件里的就不要写在HAVING中了。

1.2 慢查询日志

1.2.1 慢查询基本介绍

慢查询日志是什么?

  • MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阈值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。

  • long_query_time的默认值为10,意思是运行10秒以上的语句。

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

    说明:

    默认情况下,MySQL数据库没有开启慢查询日志需要我们手动来设置这个参数。
    当然,如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响。慢查询日志支持将日志记录写入文件。

1.2.2 慢查询日志的使用

查看慢查询日志是否开以及如何开启:

  • 查看慢查询日志是否开启:SHOW VARIABLES LIKE '%slow_query_log%';
  • 开启慢查询日志:SET GLOBAL slow_query_log = 1;使用该方法开启MySQL的慢查询日志只对当前数据库生效,如果MySQL重启后会失效。
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第6张图片
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第7张图片

慢查询日志的使用:

  • 开启后什么样的SQL会被记录到慢查询日志里
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第8张图片
  • 查看慢查询日志的阈值:SHOW VARIABLES LIKE 'long_query_time%';(默认10秒)
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第9张图片
  • 设置阈值时间:SET GLOBAL long_query_time=3;
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第10张图片
    已经生效了,但此时需要需要重新连接或新开一个会话才能看到修改值,或者使用SHOW GLOBAL VARIABLES LIKE 'long_query_time%';进行查询
  • 记录慢SQL并后续分析MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第11张图片
  • 查看当前系统中有慢查询记录数:SHOW GLOBAL STATUS LIKE '%Slow_queries%';
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第12张图片
  • 在配置中修改mysql的参数
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第13张图片

1.2.3 日志分析工具

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

查看mysqldumpslow的帮助信息:mysqldumpslow --help

# 1、mysqldumpslow --help 来查看mysqldumpslow的帮助信息
root@1dcb5644392c:/usr/bin# mysqldumpslow --help
Usage: mysqldumpslow [ OPTS... ] [ LOGS... ]
Parse and summarize the MySQL slow query log. Options are
  --verbose    verbose
  --debug      debug
  --help       write this text to standard output
  -v           verbose
  -d           debug
  -s ORDER     what to sort by (al, at, ar, c, l, r, t), 'at' is default  # 按照何种方式排序
                al: average lock time # 平均锁定时间
                ar: average rows sent # 平均返回记录数
                at: average query time # 平均查询时间
                 c: count  # 访问次数
                 l: lock time  # 锁定时间
                 r: rows sent  # 返回记录
                 t: query time  # 查询时间 
  -r           reverse the sort order (largest last instead of first)
  -t NUM       just show the top n queries  # 返回前面多少条记录
  -a           don't abstract all numbers to N and strings to 'S'
  -n NUM       abstract numbers with at least n digits within names
  -g PATTERN   grep: only consider stmts that include this string  
  -h HOSTNAME  hostname of db server for *-slow.log filename (can be wildcard),
               default is '*', i.e. match all
  -i NAME      name of server instance (if using mysql.server startup script)
  -l           don't subtract lock time from total time

MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第14张图片
工作常用参考:
MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第15张图片

1.3 批量数据脚本

往表里插入1000万条数据

  1. 建表SQL

    /* 1.dept表 */
    CREATE TABLE `dept` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
      `deptno` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '部门id',
      `dname` varchar(20) NOT NULL DEFAULT '' COMMENT '部门名字',
      `loc` varchar(13) NOT NULL DEFAULT '' COMMENT '部门地址',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='部门表'
    /* 2.emp表 */
    CREATE TABLE `emp` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
      `empno` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '员工编号',
      `ename` varchar(20) NOT NULL DEFAULT '' COMMENT '员工名字',
      `job` varchar(9) NOT NULL DEFAULT '' COMMENT '职位',
      `mgr` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '上级编号',
      `hiredate` date NOT NULL COMMENT '入职时间',
      `sal` decimal(7,2) NOT NULL COMMENT '薪水',
      `comm` decimal(7,2) NOT NULL COMMENT '分红',
      `deptno` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '部门id',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='员工表'
    
  2. 在执行创建函数之前,首先请保证 log_bin_trust_function_creators 参数为 1,即 on 开启状态。 否则会报错。
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第16张图片
    查看:SHOW VARIABLES LIKE 'log_bin_trust_function_creators';
    修改参数:SET GLOBAL log_bin_trust_function_creators=1;
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第17张图片

  3. 创建函数,保证每条数据都不同

    1. 随机产生字符串

      # DELIMITER:修改sql语句的结束符号,这里把 ; 改为 $$
      DELIMITER $$
      CREATE FUNCTION rand_string(n INT) RETURNS VARCHAR(255)
      BEGIN
      	DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
      	DECLARE return_str VARCHAR(255) DEFAULT '';
      	DECLARE i INT DEFAULT 0;
      	WHILE i < n DO
      	SET return_str = CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
      	SET i = i + 1;
      	END WHILE;
      	RETURN return_str;
      END $$
      # DELIMITER ;	#把结束符号修改回 ;
      DELIMITER ;
      
      # 删除语句
      #drop function rand_string;
      
    2. 随机产生部门编号

      DELIMITER $$
      CREATE FUNCTION rand_num() RETURNS INT(5)
      BEGIN
      	DECLARE i INT DEFAULT 0;
      	SET i = FLOOR(100+RAND()*10);
      	RETURN i;
      END $$
      DELIMITER ;
      
  4. 创建存储过程

    1. 创建往emp表中插入数据的存储过程

      DELIMITER $$
      CREATE PROCEDURE insert_emp(IN START INT(10),IN max_num INT(10))
      BEGIN
      	DECLARE i INT DEFAULT 0;
      	SET autocommit = 0;		#关闭自动提交,提高性能
      	REPEAT	#重复插入
      		SET i = i + 1;
      		INSERT INTO emp(empno,ename,job,mgr,hiredate,sal,comm,deptno)VALUES
      		((START+i),rand_string(6),'SALESMAN',0001,CURDATE(),2000,400,rand_num());
      		UNTIL i = max_num 
      	END REPEAT;
      	COMMIT;	#提交
      END $$
      DELIMITER ;
      
      # 删除语句
      # drop procedure insert_emp;
      
    2. 创建往dept表中插入数据的存储过程

      DELIMITER $$
      CREATE PROCEDURE insert_dept(IN START INT(10),IN max_num INT(10))
      BEGIN
      	DECLARE i INT DEFAULT 0;
      	SET autocommit = 0;
      	REPEAT
      	SET i = i + 1;
      	INSERT INTO dept(deptno,dname,loc)VALUES((START+i),rand_string(10),rand_string(8));
      	UNTIL i = max_num 
      	END REPEAT;
      	COMMIT;
      END $$
      DELIMITER ;
      
      # 删除语句
      drop procedure insert_dept;
      
  5. 调用存储过程

    1. 调用存储过程 insert_dept:CALL insert_dept(100,10);
      往emp表中插入10条数据,deptno从100开始。
      MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第18张图片
    2. 调用存储过程 insert_emp:CALL insert_emp(100001,500000);
      往emp表中插入50万条数据,empno从100001开始。

      MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第19张图片

1.4 Show Profile

Show Profile是什么?

Show Profile:MySQL提供可以用来分析当前会话中语句执行的资源消耗情况。可以用于SQL的调优的测量。默认情况下,参数处于关闭状态,并保存最近15次的运行结果。

分析步骤:

  1. 是否支持,看看当前的mysql版本是否支持
    查看:SHOW VARIABLES LIKE 'profiling';
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第20张图片

  2. 开启功能,默认关闭,使用前需要开启
    开启:SET PROFILing=on;
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第21张图片

  3. 运行SQL

    SELECT * FROM `emp` GROUP BY `id`%10 LIMIT 150000;
    SELECT * FROM `emp` GROUP BY `id`%20 ORDER BY 5;
    

    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第22张图片
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第23张图片

  4. 查看结果:show profiles;

    Duration:持续时间。
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第24张图片

  5. 诊断SQLshow profile cpu,block io for query 【使用show profiles查出的Query_Id】;
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第25张图片

    Show Profile查询参数备注:MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第26张图片

  6. 日常开发需要注意的结论(一定要避开该问题的出现)

    • converting HEAP to MyISAM:查询结果太大,内存都不够用了,往磁盘上搬了。
    • Creating tmp table:创建临时表(拷贝数据到临时表,用完再删除),非常耗费数据库性能。
    • Copying to tmp table on disk:把内存中的临时表复制到磁盘,危险!!!
    • locked:死锁。

1.5 全局查询日志

全局查询日志:开启后保存所有输入SQL语句,保存到mysql库里的general_log表中(安装mysql就有的)。

  1. 配置启用
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第27张图片

  2. 编码启用:
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第28张图片
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第29张图片

只能在测试环境下使用,永远不要在生产环境开启这个功能(全局查询日志),因为开启后会把所有输入的sql语句保存到表里,这样会降低性能。

二、MySQL锁机制

2.1 锁的分类

在这里插入图片描述

  1. 从对数据操作的类型(读\写)分:读锁、写锁
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第30张图片

  2. 从对数据操作的粒度分:表锁、行锁

2.2 表锁(偏读)

表锁特点:

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

2.2.1 读锁案例

  1. 建表SQL

    CREATE TABLE `mylock`(
    	`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
    	`name` VARCHAR(20)
    )ENGINE myisam;
    
    INSERT INTO `mylock`(`name`)VALUES('a');
    INSERT INTO `mylock`(`name`)VALUES('b');
    INSERT INTO `mylock`(`name`)VALUES('c');
    INSERT INTO `mylock`(`name`)VALUES('d');
    INSERT INTO `mylock`(`name`)VALUES('e');
    
    SELECT * FROM `mylock`;
    
    # 查看表上加过的锁
    show open tables;
    
    # 手动增加表锁
    lock table 表名 read(write), 表名2 read(write), 其它;
    
    # 释放表锁
    unlock tables;
    

    锁表的命令:

    1. 查看数据库表锁的命令。
      # 查看数据库表锁的命令
      SHOW OPEN TABLES;
      
    2. mylock表上读锁,给book表上写锁。
      # 给mylock表上读锁,给book表上写锁
      LOCK TABLE `mylock` READ, `book` WRITE;
      
    3. 释放表锁。
      # 释放给表添加的锁
      UNLOCK TABLES;
      
  2. 打开两个会话,SESSION1mylock表添加读锁。

    # 为mylock表添加读锁
    LOCK TABLE `mylock` READ;
    
  3. 打开两个会话,SESSION1是否可以读自己锁的表?是否可以修改自己锁的表?是否可以读其他的表?那么SESSION2呢?

    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第31张图片
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第32张图片
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第33张图片

2.2.2 写锁案例

  1. 打开两个会话,SESSION1mylock表添加写锁。
    # 为mylock表添加写锁
    LOCK TABLE `mylock` WRITE;
    
  2. 打开两个会话,SESSION1是否可以读自己锁的表?是否可以修改自己锁的表?是否可以读其他的表?那么SESSION2呢?

    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第34张图片
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第35张图片

2.2.3 案例结论

MyISAM引擎在执行查询语句SELECT之前,会自动给涉及到的所有表加读锁,在执行增删改之前,会自动给涉及的表加写锁。

MySQL的表级锁有两种模式:

  • 表共享读锁(Table Read Lock)。
  • 表独占写锁(Table Write Lock)。

MyISAM表进行操作,会有以下情况:

  • MyISAM表的读操作(加读锁),不会阻塞其他线程対同一表的读操作,但是会阻塞其他线程対同一表的写操作。只有当读锁释放之后,才会执行其他线程的写操作。
  • MyISAM表的写操作(加写锁),会阻塞其他线程対同一表的读和写操作,只有当写锁释放之后,才会执行其他线程的读写操作。

即:读锁会阻塞写,但是不会阻塞读,而写锁则会把读和写都阻塞。

2.2.4 表锁分析

可以通过Table_locks_immediateTable_locks_waited状态变量来分析系统上的表锁定。
SQL:SHOW STATUS LIKE 'table%';

mysql> SHOW STATUS LIKE 'table%';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Table_locks_immediate      | 173   |
| Table_locks_waited         | 0     |
| Table_open_cache_hits      | 5     |
| Table_open_cache_misses    | 8     |
| Table_open_cache_overflows | 0     |
+----------------------------+-------+
5 rows in set (0.00 sec)

Table_locks_immediate:产生表级锁定的次数,表示可以立即获取锁的查询次数,每立即获取锁值加1。
Table_locks_waited:出现表级锁定争用而发生等待的次数(不能立即获取锁的次数,每等待一次锁值加1),此值高则说明存在较严重的表级锁争用情况。

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

2.3 行锁(偏写)

行锁特点:

  • 偏向InnoDB存储引擎,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度最高。
  • InnoDB存储引擎和MyISAM存储引擎最大不同有两点:一是支持事务,二是采用行锁。

2.3.1 行锁案例

  1. 建表

    # 建表语句
    CREATE TABLE `test_innodb_lock`(
    `a` INT,
    `b` VARCHAR(16)
    )ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT='测试行锁'; 
    # 插入数据
    INSERT INTO `test_innodb_lock`(`a`, `b`) VALUES(1, 'b2');
    INSERT INTO `test_innodb_lock`(`a`, `b`) VALUES(2, '3');
    INSERT INTO `test_innodb_lock`(`a`, `b`) VALUES(3, '4000');
    INSERT INTO `test_innodb_lock`(`a`, `b`) VALUES(4, '5000');
    INSERT INTO `test_innodb_lock`(`a`, `b`) VALUES(5, '6000');
    INSERT INTO `test_innodb_lock`(`a`, `b`) VALUES(6, '7000');
    INSERT INTO `test_innodb_lock`(`a`, `b`) VALUES(7, '8000');
    INSERT INTO `test_innodb_lock`(`a`, `b`) VALUES(8, '9000');
    # 创建索引
    CREATE INDEX idx_test_a ON `test_innodb_lock`(a);
    CREATE INDEX idx_test_b ON `test_innodb_lock`(b);
    
  2. 行锁的基本演示
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第36张图片

    1. 行锁两个SESSION同时対一条记录进行写操作
      # SESSION1 対test_innodb_lock表的`a`=1这一行进行写操作,但是没有commit
      mysql> UPDATE `test_innodb_lock` SET `b` = '99' WHERE `a` = 1;
      Query OK, 1 row affected (0.00 sec)
      Rows matched: 1  Changed: 1  Warnings: 0
      # SESSION2 也对test_innodb_lock表的`a`=1这一行进行写操作,但是发现阻塞了!!!
      # 等SESSION1执行commit语句之后,SESSION2的SQL就会执行了
      mysql> UPDATE `test_innodb_lock` SET `b` = 'asdasd' WHERE `a` = 1;
      ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
      
    2. 行锁两个SESSION同时对不同记录进行写操作
      # SESSION1 対test_innodb_lock表的`a`=6这一行进行写操作,但是没有commit
      mysql> UPDATE `test_innodb_lock` SET `b` = '8976' WHERE `a` = 6;
      Query OK, 1 row affected (0.00 sec)
      Rows matched: 1  Changed: 1  Warnings: 0
      # SESSION2 対test_innodb_lock表的`a`=4这一行进行写操作,没有阻塞!!!
      # SESSION1和SESSION2同时对不同的行进行写操作互不影响
      mysql> UPDATE `test_innodb_lock` SET `b` = 'Ringo' WHERE `a` = 4;
      Query OK, 1 row affected (0.00 sec)
      Rows matched: 1  Changed: 1  Warnings: 0
      
  3. 无索引(或索引失效)行锁升级为表锁

    # SESSION1 执行SQL语句,没有执行commit。
    # 由于`b`字段是字符串,但是没有加单引号导致索引失效
    mysql> UPDATE `test_innodb_lock` SET `a` = 888 WHERE `b` = 8000;
    Query OK, 1 row affected, 1 warning (0.00 sec)
    Rows matched: 1  Changed: 1  Warnings: 1
    # SESSION2 和SESSION1操作的并不是同一行,但是也被阻塞了???
    # 由于SESSION1执行的SQL索引失效,导致行锁升级为表锁。
    mysql> UPDATE `test_innodb_lock` SET `b` = '1314' WHERE `a` = 1;
    ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
    
  4. 间隙锁的危害
    MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第37张图片
    什么是间隙锁?

    当我们用范围条件而不是相等条件检索数据,并请求共享或者排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁,对于键值在条件范文内但并不存在的记录,叫做"间隙(GAP)"。
    InnoDB也会对这个"间隙"加锁,这种锁的机制就是所谓的"间隙锁"。

    间隙锁的危害

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

  5. 如何锁定一行

MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第38张图片

2.3.2 案例结论

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

2.3.3 行锁分析

通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况
SQL:SHOW STATUS LIKE '表名%';

MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第39张图片

対各个状态量的说明如下:

  • Innodb_row_lock_waits:系统启动后到现在总共等待的次数(重要)。
  • Innodb_row_lock_time:从系统启动到现在锁定总时间长度(重要)。
  • Innodb_row_lock_time_avg:每次等待所花的平均时间(重要)。
  • Innodb_row_lock_time_max:从系统启动到现在等待最长的一次所花的时间。
  • Innodb_row_lock_current_waits:当前正在等待锁定的数量。

尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手制定优化策略。

2.3.4 优化建议

MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第40张图片

2.4 页锁(了解)

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

三、主从复制

3.1 复制基本原理

好的博客:MySQL主从复制读写分离,看这篇就够了!

MySQL的主从复制

  • 主从复制、读写分离一般是一起使用的。目的很简单,就是为了提高数据库的并发性能(master复制读写,slave负责读)
  • 同时,随着业务量的扩展、如果是单机部署的MySQL,会导致I/O频率过高。采用主从复制、读写分离可以提高数据库的可用性

主从复制的原理:

  • slave的IO线程向master请求从指定的binlog日志文件的指定位置之后的binlog日志内容
  • salve从库连接master主库,Master有多少个slave就会创建多少个binlog dump线程。
  • 当Master节点进行insert、update、delete操作时,会按顺序写入到binlog中。
  • 当Master节点的binlog发生变化时,binlog dump 线程会通知所有的salve节点,并将相应的binlog内容推送给slave节点
  • I/O线程接收到 binlog 内容后,将内容写入到本地的 relay-log
  • SQL线程读取I/O线程写入的relay-log,并且根据 relay-log 的内容对从数据库做对应的操作

MySQL高级——查询优化、慢查询日志、锁机制、主从赋值_第41张图片

主从复制三个线程:

  • 主节点:dump Thread:为每个Slave的I/O Thread启动一个dump线程,用于向其发送binary log events
  • 从节点:I/O Thread:向Master请求二进制日志事件,并保存于中继日志中
  • 从节点:SQL Thread:从中继日志中读取日志事件,在本地完成重放

3.2 一主一从配置实战(不全)

1、基本要求:Master和Slave的MySQL服务器版本一致且后台以服务运行。

# 创建mysql-slave1实例
docker run -p 3307:3306 --name mysql-slave1 \
-v /root/mysql-slave1/log:/var/log/mysql \
-v /root/mysql-slave1/data:/var/lib/mysql \
-v /root/mysql-slave1/conf:/etc/mysql \
-e MYSQL_ROOT_PASSWORD=333 \
-d mysql:5.7

2、主从配置都是配在[mysqld]节点下,都是小写

修改主库配置文件my.conf

# Master配置
[mysqld]
server-id=1 # 必须
log-bin=/var/lib/mysql/mysql-bin # 必须
read-only=0
binlog-ignore-db=mysql

修改从库配置文件my.conf

# Slave配置
[mysqld]
server-id=2 # 必须
log-bin=/var/lib/mysql/mysql-bin

3、Master配置,在主库创建同步用户

# 1、GRANT REPLICATION SLAVE ON *.* TO 'username'@'从机IP地址' IDENTIFIED BY 'password';
mysql> GRANT REPLICATION SLAVE ON *.* TO 'zhangsan'@'172.18.0.3' IDENTIFIED BY '123456';
Query OK, 0 rows affected, 1 warning (0.01 sec)
# 2、刷新命令
mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)
# 3、记录下File和Position
# 每次配从机的时候都要SHOW MASTER STATUS;查看最新的File和Position
mysql> SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+-------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 |      602 |              | mysql            |                   |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)

4、Slave从机配置

CHANGE MASTER TO MASTER_HOST='172.18.0.4',
MASTER_USER='zhangsan',
MASTER_PASSWORD='123456',
MASTER_LOG_FILE='mysql-bin.File的编号',
MASTER_LOG_POS=Position的最新值;
# 1、使用用户名密码登录进Master
mysql> CHANGE MASTER TO MASTER_HOST='172.18.0.4',
    -> MASTER_USER='zhangsan',
    -> MASTER_PASSWORD='123456',
    -> MASTER_LOG_FILE='mysql-bin.000001',
    -> MASTER_LOG_POS=602;
Query OK, 0 rows affected, 2 warnings (0.02 sec)
# 2、开启Slave从机的复制
mysql> START SLAVE;
Query OK, 0 rows affected (0.00 sec)
# 3、查看Slave状态
# Slave_IO_Running 和 Slave_SQL_Running 必须同时为Yes 说明主从复制配置成功!
mysql> SHOW SLAVE STATUS\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event # Slave待命状态
                  Master_Host: 172.18.0.4
                  Master_User: zhangsan
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000001
          Read_Master_Log_Pos: 602
               Relay_Log_File: b030ad25d5fe-relay-bin.000002
                Relay_Log_Pos: 320
        Relay_Master_Log_File: mysql-bin.000001
             Slave_IO_Running: Yes  
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 602
              Relay_Log_Space: 534
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 1
                  Master_UUID: bd047557-b20c-11ea-9961-0242ac120002
             Master_Info_File: /var/lib/mysql/master.info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
           Master_Retry_Count: 86400
                  Master_Bind: 
      Last_IO_Error_Timestamp: 
     Last_SQL_Error_Timestamp: 
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
           Retrieved_Gtid_Set: 
            Executed_Gtid_Set: 
                Auto_Position: 0
         Replicate_Rewrite_DB: 
                 Channel_Name: 
           Master_TLS_Version: 
1 row in set (0.00 sec)

5、测试主从复制

# Master创建数据库
mysql> create database test_replication;
Query OK, 1 row affected (0.01 sec)
# Slave查询数据库
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| test_replication   |
+--------------------+
5 rows in set (0.00 sec)

6、停止主从复制功能

# 1、停止Slave
mysql> STOP SLAVE;
Query OK, 0 rows affected (0.00 sec)
# 2、重新配置主从
# MASTER_LOG_FILE 和 MASTER_LOG_POS一定要根据最新的数据来配
mysql> CHANGE MASTER TO MASTER_HOST='172.18.0.4',
    -> MASTER_USER='zhangsan',
    -> MASTER_PASSWORD='123456',
    -> MASTER_LOG_FILE='mysql-bin.000001',
    -> MASTER_LOG_POS=797;
Query OK, 0 rows affected, 2 warnings (0.01 sec)
mysql> START SLAVE;
Query OK, 0 rows affected (0.00 sec)
mysql> SHOW SLAVE STATUS\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 172.18.0.4
                  Master_User: zhangsan
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.000001
          Read_Master_Log_Pos: 797
               Relay_Log_File: b030ad25d5fe-relay-bin.000002
                Relay_Log_Pos: 320
        Relay_Master_Log_File: mysql-bin.000001
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 797
              Relay_Log_Space: 534
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 1
                  Master_UUID: bd047557-b20c-11ea-9961-0242ac120002
             Master_Info_File: /var/lib/mysql/master.info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
           Master_Retry_Count: 86400
                  Master_Bind: 
      Last_IO_Error_Timestamp: 
     Last_SQL_Error_Timestamp: 
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
           Retrieved_Gtid_Set: 
            Executed_Gtid_Set: 
                Auto_Position: 0
         Replicate_Rewrite_DB: 
                 Channel_Name: 
           Master_TLS_Version: 
1 row in set (0.00 sec)

你可能感兴趣的:(MySQL,数据库,mysql)