MySQL进阶及优化

jefxff 153,572 2020-05-02

1. MySQL安装及配置:

  • 5.4 - 5.x : MySQL整合了三方公司的新存储引擎 (使用 5.5)

Linux (CentOS x) 安装:

  • 安装: rpm -ivh rpm软件名

    • rpm -ivh MySQL-server-5.5.58-1.el6.x86_64.rpm

    • rpm -ivh MySQL-client-5.5.58-1.el6.x86_64.rpm

  • 启动: service mysql start

  • 关闭: service mysql stop

  • 重启: service mysql restart

  • 可能遇到的错误:

    • 如果安装时与某个软件 xxx 冲突, 则需要将冲突的软件卸载掉: yum -y remove xxx

    • 如果提示 "GPG keys ..." 安装失败, 解决方案: rpm -ivh rpm软件名 --force --nodoeps

    • 在重启Linux 之后, 登录MySQL, 可能报错: "/var/lib/mysql/mysql.sock不存在"; 原因: 是MySQL的服务没有启动

      • 解决1: 每次使用前, 手动启动服务: /etc/init.d/mysql start

      • 解决2: 设置开机自启: chkconfig mysql on; (关闭自启: chkconfig mysql off)

      • 检查是否开机自启: ntsysv

    • 安装完成后, 日志有提示修改密码: /usr/bin/mysqladmin -u root password 'new_password'


MySQL 数据存放目录

  • 通过命令来查看: ps -ef | grep mysql

  • MySQL 核心目录

    • /usr/share/mysql : 配置文件目录

    • /usr/bin : 命令目录 (mysqladmin, mysqldump 等命令)

    • 安装及数据存放目录: datadir = /var/lib/mysql

    • MySQL的启停脚本: /etc/init.d/mysql


MySQL 配置文件

  • mysql 在初始化时会有很多配置文件, 包括: my-huge.cnf, my-large.cnf, my-medium.cnf, my-small.cnf 等等, 这些配置文件是根据服务器的性能来决定使用的, 但是MySQL不支持这些配置文件; MySQL默认只支持 /etc/my.cnf; 因此需要将上述的配置文件复制一份为 /etc/my.cnf

  • 命令: cp /usr/share/mysql/my-huge.cnf /etc/my.cnf

    • 注意: mysql5.5默认配置文件/etc/my.cnf; mysql5.6默认配置文件/etc/mysql-default.cnf

MySQL 字符编码

  • 在登录MySQL后执行(SQL语句)即可查看MySQL的编码: show variables like '%char%';

  • 注意: 修改编码只对修改之后创建的数据库生效, 所以在MySQL安装完毕后, 第一时间修改编码

  • 在/etc/my.cnf 中修改:

    • 在 [mysql] 中增加: default-character-set=utf8

    • 在 [client] 中增加: default-character-set=utf8

    • 在[mysqld] 中增加:

      • character_set_server=utf8

      • character_set_client=utf8

      • collation_server=utf8_general_ci

  • 重启 MySQL 服务: service mysql start


2. MySQL 原理

MySQL 分层

  • MySQL 逻辑分层: 连接层(处理与客户端的连接) --> 服务层(使用接口, SQL优化) --> 引擎层(InnoDB, MyISAM) --> 存储层

    • InnoDB (MySQL默认) : 事务优先, 适合高并发, 使用的是行锁(即加锁时锁一行数据)

    • MyISAM : 性能优先, 使用的是表锁(一次锁一张表)

  • 引擎相关的SQL命令:

    • 查看MySQL支持那些引擎: show engines;

    • 查看当前使用的引擎: show variables like '%storage_engine%';

  • 在创建数据库或者表时可以指定使用的引擎及字符编码

-- 创建表时指定引擎为 MyISAM, 指定字符编码为utf8
create table tb(
	id int(4) auto_increment primary key,
	name varchar(5),
	dept varchar(5)
)ENGINE=MyISAM 
 DEFAULT CHARSET=utf8;

3. SQL 优化

  • 原因: 性能低, 执行时间长, 等待时间长, SQL语句欠佳(尤其是连接查询), 索引失效, 服务器参数设置不合理(缓冲区及线程数)

SQL 语句优化:

  • sql语句编写过程: select dinstinct .. from .. join .. on .. where .. group by ... having .. order by .. limit ..

  • sql语句解析过程: from .. on .. join .. where .. group by .. having .. select dinstinct .. order by .. limit ..

  • SQL 语句优化: 主要是优化索引

    • 索引:(相当于书的目录) index 是帮助MySQL高效获取数据的数据结构; 索引是数据结构(树: B树(MySQL默认), Hash树 ...)

    • 索引的弊端:

      • 索引本身很大, 可以存放在内存/硬盘(通常为硬盘)

      • 索引不是所有情况均适用: 少量数据, 频繁更新的字段, 很少使用的字段不适合使用索引

      • 索引会降低增删改的效率

    • 索引的优势:

      • 提高查询的效率(降低IO的使用率)

      • 降低CPU使用率(如: order by desc; 因为B树索引本身就是一个排好序的索引, 因此在使用索引时, 可以直接使用)

    • B 树:(使用的是B+树)

      • 3层的B树可以存放上百万条数据

      • B 树一般指的是 B+ 树, 数据全部存放在叶子节点

      • B+ 树中查询的任意数据次数: n次 (n代表B树的高度)


4. 索引

索引的分类

  • 单值索引 : 使用单列作为索引, 一个表可以有多个单值索引

  • 主键索引 : 不能重复, 一般使用 id, 且不能为 null

  • 唯一索引 : 索引不能重复, 唯一索引一般使用 id; 唯一索引可以是 null

  • 复合索引 : 多个列构成的索引(相当于书的二级目录); 注意复合索引不一定要求所有列都中


创建索引

  • 语法(创建索引方式一): create 索引类型 索引名 on 表(字段)

  • 语法(创建索引方式二): alter table 表名 add 索引类型 索引名 (字段)

  • 注: 如果一个字段是 primary key, 则该字段默认就是主键索引

  • 例子1(创建索引方式一):

    • 单值索引(直接写index, 索引类型就是单值索引): create index dept on tb(dept);

    • 唯一索引(unique index): create unique index name on tb(name);

    • 复合索引(也用index, 和单值索引的区别在后面的列名处): create index dept_name_index on tb(dept, name);

  • 例子2(创建索引方式二):

    • 单值索引: alter table tb add index dept (dept);

    • 唯一索引: alter table tb add unique index name (name);

    • 复合索引: alter table tb add index dept_name_index (dept, name);


删除索引

  • 语法: drop index 索引名 on 表;

  • 例子:

    • drop index name on tb;

查看索引

  • 语法: show index from 表;

  • 例子:

    • show index from tb;

SQL 性能问题

  • 分析SQL的执行计划: 关键字 explain : 可以模拟SQL优化器执行SQL语句, 从而让开发人员知道自己编写的SQL状况

  • MYSQL 查询优化器会干扰我们的优化

  • 执行查询计划:

    • 语法: explain SQL语句
    • 例如: explain select * from tb;

explain + SQL 的执行结果表头字段说明:

  • 字段值: id(编号); select_type(查询类型); table(表); type(类型); possible_keys(预测用到的索引); key(实际使用的索引); key_len(实际使用索引的长度); ref(表之间的引用); rows(通过索引查询到的数据量); Extra(额外的信息)

  • id :

    • id 值相同(多表查询): 从上往下顺序执行; 表的执行顺序因数量的个数改变而改变(原因:笛卡尔积); 数据小的表, 优先查询

    • id 值不同(子查询): id值越大越优先查询 (原因: 在嵌套子查询时, 先查内层,再查外层)

    • id 值有相同, 又有不同: id 值越大越优先, id值相同, 从上往下 顺序执行

  • select_type :

    • PRIMARY : 包含子查询SQL中的主查询, (最外层)

    • SUBQUERY : 包含子查询SQL中的子查询 (非最外层)

    • SIMPLE : 简单查询 (不包含 子查询, 不包含 unique连接查询)

    • DERIVED : 衍生查询 (使用到了临时表)

      • 在 from 子查询中只有一张表(如: explain select cr.cname from (select * from course where tid in (1,2)) cr;)

      • 在 from 子查询中, 如果有 table1 union table2, 则 table1 就是 derived(衍生表), table2 就是 union

        • 如: explain select cr.cname from (select * from course where tid = 1 union select * from course where tid = 2) cr;
    • UNION : 如上例 table2 就是 union

    • UNION RESULT : 告知开发人员那些表之间存在 union 查询

  • type : 索引类型

    • 结果值从好到坏排序: system > const > eq_ref > ref > range > index > all

    • 其中: system, const 只是理想情况; 实际能达到的在 ref 后面

    • system : 只有一条数据的系统表; 或衍生表只有一条数据的主查询

    • const : 仅仅能查到一条数据的SQL, 用于 primary key 或 unique 索引 (查询类型与索引类型有关)

    • eq_ref : 唯一性索引(前提 该字段唯一, 且为该字段添加了索引; 常用于主键索引或唯一索引): 对于每个索引键的查询, 返回匹配唯一行数据(有且只有1个, 不能多, 不为0)

      • 增加主键语法: alter table 表名 add constraint pk_主键字段 primary key(主键字段);

      • 增加唯一约束语法: alter table 表名 add constraint uk_唯一字段 unique index(唯一字段);

      • 增加外键约束语法: alter table 表1 add constraint fk_表1_表2_字段 foreign key(外键字段) references 表2(外键字段);

    • ref: 非唯一性索引, 对于每个索引键的查询, 返回匹配的所有行(0个或多个)

    • range : 检查指定范围的行, where 后面是一个范围查询(between, >, <, >=, in(有时候会失效, 从而转为all) )

    • index : 查询全部索引中的数据

      • explain select tid from teacher; (tid已经是索引, 所以前面这条SQL语句的type是index)
    • all : 查询全部表数据

      • explain select cid from course; (cid 不是索引, 需要全表扫描, 即需要所有表中的所有数据)
  • possible_keys : 可能用到的索引, 是一种预测; 如果值是NULL, 则说明没用索引

  • key: 实际使用到的索引

  • key_len : 索引的长度; 作用: 用于判断复合索引是否被完全使用

    • 在MySQL中ut8是一个字符占3个字节; (GDK:1个字符2个字节; latin:一个字符一个字节)

    • 如果索引字段可以为NULL, 则会使用1个字节用于标识

    • MySQL使用两个字节用来标识可变长度(varchar)

  • ref: 用于指明当前表所参照的字段

  • rows: 被索引优化查询的数据的个数(实际通过索引而查询到的数据的个数)

  • extra:

    • using filesort: 性能消耗大; 需要 "额外" 一次排序(查找) (常见于 order by)

      • 对于单索引, 如果排序和查找的是同一个字段, 则不会出现 using fileout; 如果排序和查找的不是同一个字段, 则会出现 using fileout; 避免: where 那个字段, 就order by 那个字段

      • 复合索引: 不能跨列(最佳左前缀) : where 和 order by 按照复合索引的顺序使用, 不要跨列或无序使用

    • using temporary: 性能损耗较大, 用到了临时表 (常见于 group by 语句中); 所以: 查询哪些列, 就根据那些列来 group by..

    • using index: 性能提升; 索引覆盖(覆盖索引: 使用到的列全在索引中); 原因: 不读取原文件, 只从索引文件中获取数据(不需要回表查询)

      • 如果用到了索引覆盖(using index时), 会对 possible 和 key 造成影响

        • 如果没有 where, 则索引只出现在key中

        • 如果有 where,则索引会出现在 possible 和 key 中

    • using where : 需要回原表查询

    • impossable where : where 子句永远为 false

    • using join buffer : MySQL引擎使用的连接缓存, 说明SQL写的太差了, 需要优化

    • 总结:

      • 如果 (a, b, c, d) 复合索引, 和使用的顺序全部一致(且不跨列使用), 则复合索引全部使用, 如果部分一致(且不跨列使用), 则使用部分索引

      • where 和 order by 拼起来, 不要跨列使用


explain 查询结果总结

  • system/const: 结果只有一条数据

  • eq_ref: 结果多条, 但是每条数据是唯一的

  • ref: 结果多条, 但是每条数据是0或多条


优化案例

单表优化

  • 创建表增加数据
create table book (
	bid int(4) primary key,
	name varchar(20) not null,
	authorid int(4) not null,
	publicid int(4) not null,
	typeid int(4) not null
);

insert into book values(1, 'tjava', 1, 1, 2);
insert into book values(2, 'tc', 2, 1, 2);
insert into book values(3, 'wx', 3, 2, 1);
insert into book values(4, 'math', 4, 2, 3);
  • 查询语句
explain select bid from book where authorid=1 and typeid in (2, 3) order by typeid desc;
  • 优化: 加索引

    • alter table book add index idx_bta (bid, typeid, authorid); (删除: drop index idx_bta on book;)

      • 执行查询语句的结果: type: index, Extra:using filesort; Using where; Backward index scan; Using index
    • 进一步优化, 按照SQL的执行顺序增加索引: alter table book add index idx_tab (typeid, authorid, bid);

      • 执行查询语句的结果: type: index Extra: Using where; Backward index scan; Using index
    • 进一步优化: 思路: 因为范围查询in有时候会失效, 所以索引也应该按照atb的形式来建

      • drop index idx_tab on book;

      • 创建: alter table book add index idx_atb (authorid, typeid, bid);

  • 总结:

    • 最佳左前缀, 保持索引的定义和使用顺序一致

    • 索引需要逐步优化

    • 将含 in 的范围查询放在where条件的最后, 防止失效

    • 上例中: Extra:Using where(需要回原表); Using index(不需要回原表); 原因: SQL 查询语句中 authorid 在索引中(直接在索引中能查到), 但是 typeid in 会使索引失效, 因此相当于没有 typeid 这个索引, 所以需要回原表( using where)


双表优化

  • 创建表及增加数据
	create table teacher2 (
		tid int(4) primary key,
		cid int(4) not null
	);

	insert into teacher2 values(1,2);
	insert into teacher2 values(2,1);
	insert into teacher2 values(3,3);

	create table course2 (
		cid int(4) primary key,
		cname varchar(20)
	);

	insert into course2 values (1, 'java');
	insert into course2 values (2, 'pythton');
	insert into course2 values (3, 'kotlin');
  • SQL查询语句
	select * from teacher2 t left join course2 c
	on t.cid=c.cid where c.cname='java';

	explain select * from teacher2 t left outer join course2 c
	on t.cid=c.cid where c.cname='java';
  • 优化: 加索引

    • 原则: 1. (小表驱动大表) select ... where 小表=大表; 2. 索引建立在经常使用的字段上[一般情况下左外连接给坐表加索引, 右外连接给右表加连接]

    • 加索引: alter table teacher2 add index index_teacher2_cid (cid);

    • 加索引: alter table course2 add index index_course2_cname (cname);


三表优化

  • 小表驱动大表

  • 索引建立在经常查询的字段上


7. 避免使索引失效的一些原则

  • 复合索引, 不要跨列或无序使用(最佳左前缀)

    • 如索引: (a, b, c); 使用时: where a ... and b ... order by c;
  • 复合索引, 尽量使用全索引匹配

    • 如索引: (a, b, c); 使用的时候索引全部使用, 不要使用两个, 或一个
  • 不要在索引上进行任何操作(计算, 函数, 类型转换), 否则索引失效

  • 复合索引不能使用不等于(!= <>) 或 is null (is not null), 否则自身以及右侧索引全部失效

  • 尽量不要使用类型转换(显示, 隐式), 如: varchar字段使用了int类型去查询, 则索引失效

  • where语句中尽量不要使用 or, 使用 or 但是没有把 or 的所有字段加上索引会使 索引失效

  • where 语句中使用 not in, in(有时候会失效)

  • like 模糊查询尽量以 "常量" 开头, 不能使用 "%" 开头, 否则索引失效

  • 综上: 尽量使用索引覆盖(using index);(查询的字段全部在索引里面)


一些其他的优化方法

  • exists 和 in 优化

    • 如果主查询的数据集大, 则使用 in; 如果子查询的数据集大, 则使用 exists

    • exist 语法: select ... from table where exists (子查询);

      • 说明: 将主查询的结果, 放在子查询结果中互进行条件校验(看子查询是否有数据, 如果有数据则校验成功), 如果符合校验, 则保留数据; 否则不保留数据

      • 如: select tname from teacher where exists (select * from teacher); (等价于: select tname from teacher;)

    • in 语法: select ... from table where 字段 in (子查询):

      • 如: select tcid from teacher where tcid in (select tcid from teacherCard);
  • order by 优化

    • 使用order by 时, 经常在Extra中会出现: using filesort; using filesort 底层有两种算法(根据IO算法): 双路排序, 单路排序; (mysql 4.1之前默认双路, MySQL4.1之后默认是单路)

    • 双路排序: 1. 第一次从磁盘读取排序字段, 对排序字段进行排序(在Buffer中进行排序); 2. 第二次扫描其他字段

    • 单路排序: 只读取一次(全部字段), 在Buffer中进行排序, 但这种单路排序会有一定的隐患 (不一定真的是 "单路|1次IO", 有可能多次IO); 原因: 如果数据量特别大, 无法将所有字段的数据一次性读取完毕, 因此会进行 "分片" 读取

      • 单路排序会比双路排序占用更多的Buffer; 单路排序在使用时, 如果数据大, 可以考虑调大Buffer的容量大小

      • 调大Buffer的SQL: set max_length_for_sort_data = 1024;

      • 如果 set max_length_for_sort_data 设置的值太低(需要排序的列的总大小超过了max_length_for_sort_data定义的字节数), MySQL会自动从单路切换到双路;

    • 提高 order by 查询的策略

      • 选择使用单路, 双路; 调整buffer的容量大小

      • 避免使用: select * ...

      • 复合索引不要跨列使用, 避免出现 using filesort

      • 保证全部的排序字段, 排序的一致性(全升或全降)


9. SQL排查-慢查询日志

  • 慢查询日志: MySQL提供的一种日志记录, 用于记录MySQL各种响应时间超过阀值的SQL语句(long_query_time, 默认10秒)

  • 默认情况下, 慢查询日志是关闭的, 所以开发调优时打开, 而最终部署时关闭

  • 检查是否开启慢查询日志SQL: show variables like '%slow_query_log%';


开启慢查询日志

  • 临时开启(MySQL服务关闭时关闭慢查询) : set global slow_query_log = 1; -- 在内存里面开启; 1是开启, 0是关闭

  • 永久开启: 编辑: /etc/my.conf 配置文件, 在[mysqld]下面增加

    • slow_query_log=1

    • slow_query_log_file=/var/lib/mysql/localhost-slow.log


慢查询阀值:

  • 查看慢查询日志的阀值: show variables like '%long_query_time%';

  • 设置慢查询日志的阀值:

    • 临时设置阀值(重新登录后生效): set global long_query_time = 5;
  • 永久开启: 编辑: /etc/my.conf 配置文件, 在[mysqld]下面增加

    • long_query_time = 5

查询超过阀值的SQL:show global status like '%slow_queries%';

  • 慢查询的sql被记录在日志中, 可以通过查看日志, 检查具体的慢SQL

  • 通过mysqldumpslow工具查看慢SQL: 可以i通过一些过滤条件, 快速查找出需要定位的慢SQL

  • mysqldumpslow 常用参数

    • s : 排序方式

    • r : 逆序

    • i : 锁定时间

    • g : 正则匹配模式


mysqldumpslow 实用例子:

  • mysqldumpslow 语法: mysqldumpslow 各种参数 慢查询日志文件的位置

  • 获取返回记录最多的3个SQL

    - mysqldumpslow -s r -t 3 /var/lib/mysql/localhost-slow.log

  • 获取访问次数最多的3个SQL

    - mysqldumpslow -s c -t 3 /var/lib/mysql/localhost-slow.log

  • 按照时间排序, 前10条包含left join查询语句的SQL

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

10. 分析海量数据

  • 模拟海量数据, 使用存储过程(无 return)/存储函数(有 return)
	create database testdata;

	use testdata;

	create table dept (
		dno int(5) primary key default 0,
		dname varchar(20) not null default '',
		loc varchar(30) default ''
	)engine=innodb default charset=utf8;

	create table emp (
		eid int(5) primary key,
		ename varchar(20) not null default '',
		job varchar(20) not null default '',
		deptno int(5) not null default 0
	) engine=innodb default charset=utf8;

通过存储函数插入海量数据:

  • 创建存储函数:
	-- 创建randstring() 存储函数用于模拟员工的名称
	delimiter $
	create function randstring(n int) returns varchar(255)
	begin 
		declare all_str varchar(100) default 'abcdefghigklmnopqrstubwxyzABCDEFGHIGKLMNOPQRSTUVWXZY';
		declare return_str varchar(255) default '';
		declare i int default 0;
		while i < n 
		do 
			set return_str = concat(return_str, substring(all_str, FLOOR(1+rand()*52), 1));
			set i = i+1;
		end while;
		return return_str;
	end$


	-- 产生随机整数
	create function ran_num() returns int(5)
	begin 
		declare i int default 0;
		set i = floor(rand()*100);
		return i;

	end$
  • 存储函数报错了解:

    • You have an error in your SQL syntax; 说明SQL语句错误, 修改SQL语句

    • This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary logging is enabled ; 是因为存储过程/存储函数在创建时与之前的开启慢查询日志冲突了

    • 临时解决: (开启 log_bin_trust_function_creators )

      • 查看是否开启: show variables like '%log_bin_trust_function_creators%';

      • 开启: set global log_bin_trust_function_creators = 1;

    • 永久解决: 修改 /etc/my.conf; 在 [mysqld]后面追加

      • log_bin_trust_function_creators = 1
  • 创建存储过程插入海量数据, emp表

	create procedure insert_emp(in eid_start int(10), in data_times int(10))
	begin	
		declare i int default 0;
		set autocommit = 0;

		repeat 
			insert into emp values(eid_start + i, randstring(5), 'other', ran_num());
			set i=i+1;
			until i=data_times
		end repeat;
		commit ;
	end $
  • 创建存储过程插入海量数据, dept表
	create procedure insert_dept(in dno_start int(10), in data_times int(10))
	begin	
		declare i int default 0;
		set autocommit = 0;

		repeat 
			insert into dept values(dno_start + i, randstring(6), randstring(8));
			set i=i+1;
			until i=data_times
		end repeat;
		commit ;
	end $
  • 插入数据

    • 将SQL的语句结束符改回来: delimiter ;

    • 调用存储函数给emp表增加80万条数据: call insert_emp(1000, 800000);

    • 调用存储过程给dept表增加30条数据: call insert_dept(10, 30);


分析海量数据

profiles

  • 查看是否开启(默认是关闭的): show variables like '%profiling%';

  • 开启: set profiling = on;

  • show profiles: 在打开 profiling之后, 会记录全部SQL查询语句所花费的时间; 缺点: 只能看到总共花费的时间, 但是不能看到各个硬件消费的时间(如: cpu, io)

精确分析: sql诊断

  • 诊断全部信息SQL命令(其中query_Id是show profiles查询到的): show profile all for query query_Id;

    • 如: show profile all for query 1;
  • 诊断CPU, IO相关的信息的命令: show profile cpu, block io for query query_id;

    • 如: show profile cpu, block io for query 1;

全局的查询日志

  • 记录开启之后的全部SQL语句, (这些全局的记录操作, 仅仅在调优, 开发过程中打开, 在实际部署时一定要关闭)

  • 开启后会记录所有的SQL: 记录在 mysql.general_log表中或文件中

  • 查看全局日志: show variables like '%general_log%';

  • 开启全局日志:

    • 将SQL记录在mysql.general_log 表中

      • set global general_log = 1;

      • set global log_output='table';

    • 将SQL记录在本机文件中

      • set global log_output='file';

      • set global general_log = 1;

      • set global general_log_file='/tmp/general.log';


11. 锁机制

  • 解决因资源共享而造成的并发问题

锁分类

  • 操作类型:

    • 读锁(共享锁): 对同一个数据, 多个读操作可以同时进行, 互不干扰

    • 写锁(互斥锁): 如果当前写操作没有完毕, 则无法进行其他的读操作, 写操作

  • 操作范围

    • 表锁: 一次性对一张表整体加锁; 如: MySIAM 存储引擎使用的是表锁, 开销小, 加锁快; 无死锁, 但锁的范围大, 容易发生锁冲突, 并发度低

    • 行锁: 一次性对一条数据加锁; 如: InnoDB 使用的就是行锁, 开销大, 加锁慢, 容易出现死锁, 但锁的范围小, 不易发生锁冲突, 并发度高(很小概率发生并发问题: 脏读, 幻读, 不可重复读, 丢失更新等问题)

    • 页锁

表锁:

  • 创建示例表并增加数据
	create table tablelock (
		id int primary key auto_increment,
		name varchar(20)
	) engine=myisam default charset=utf8;

	insert into tablelock(name) values('a1');
	insert into tablelock(name) values('a2');
	insert into tablelock(name) values('a3');
	insert into tablelock(name) values('a4');
	insert into tablelock(name) values('a5');
  • 增加读写锁语法: lock table 表1 read/write, 表2 read/write, ...

  • 查看加锁的表(1代表加锁): show open tables;

  • 分析表锁定的严重程度: show status like 'table%';

    • Table_locks_immediate: 即可能获取到的表数

    • Table_locks_waited : 需要等待的表锁数(如果该值越大, 说明存在越大的锁竞争)

    • 一般: Table_locks_immediate/Table_locks_waited > 5000 , 则使用InnoDB 引擎, 否则使用MyISAM引擎

锁示例

  • 加读锁(当前会话0的效果: 只能读当前加锁的表, 不能对其他表进行任何操作): lock table tablelock read;

    • 对会话0的影响: 如果某一个会话对A表进行了read锁, 则该会话可以对A表进行读操作, 不能进行写操作, 且该会话不能对其他表进行读/写操作

    • 对其他会话的影响: 会话0给A表加锁, 其他会话的操作: 1. 可以对其他表(A表以外的表)进行读/写操作; 2. 对A表, 可以读操作, 但是写操作需要等待会话0释放锁

    • 释放锁: unlock tables;

  • 加写锁: lock table tablelock write;

    • 对当前会话0的影响: 可以对加了锁的表进行任何操作(增删改查); 但是不能对其他表进行任何操作

    • 对其他会话的影响: 对会话0中加写锁的表可以进行增删改查的前提是: 等待会话0释放写锁; 对会话0除加锁以外的表可以进行任意的操作(增删改查)


行锁

  • 创建示例表并增加数据
	create table linelock (
		id int primary key auto_increment,
		name varchar(20)
	) engine=InnoDB default charset=utf8;

	insert into linelock(name) values('a1');
	insert into linelock(name) values('a2');
	insert into linelock(name) values('a3');
	insert into linelock(name) values('a4');
	insert into linelock(name) values('a5');
  • mysql 默认是自动commit, 而Oracle默认不会自动commit, 需要手工commit; 为了学习行锁, 暂时关闭MySQL的自动commit

    • 关闭自动提交: set autocommit = 0;

    • 开启自动提交: set autocommit = 1;

  • 结论:

    • 如果会话x对某条数据a进行DML操作(需要在关闭自动提交的情况下), 则其他会话必须等到该会话结束事务(执行 commit/rollback操作后)后, 才能对数据a进行操作

    • 表锁是通过 unlock tables 或 事务来释放锁, 行锁是通过事务释放锁

    • InnoDB 默认使用的是行锁; 缺点: 比表锁性能损耗大, 优点: 并发能力强, 效率高; 因此: 高并发使用Innodb, 否则使用MyISAM

  • 注意:

    • 如果没有索引, 则行锁会转为表锁

    • 间隙锁: 行锁的一种特殊情况: 值在范围内, 但却不存在; 对于间隙: MySQL会给间隙枷锁, 就称为间隙锁

行锁分析

  • show status like '%innodb_row_lock%';

    • Innodb_row_lock_current_waits : 当前正在等待锁的数量

    • Innodb_row_lock_time : 等待总时长, 从系统启动到现在一共等待的时间

    • Innodb_row_lock_time_avg : 平均等待时长, 从系统启动到现在平均等待的时间

    • Innodb_row_lock_time_max : 最大等待时长, 从系统启动到现在最大一次等待的时间

    • Innodb_row_lock_waits : 等待次数, 从系统启动到现在最大一次等待的时间


# mysql