主要体现在存储引擎的架构上,插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取想分离。这种架构可以根据业务的需求和实际需求选择合适的 存储引擎。
①查看存储引擎的命令
②MyISAM和InnoDB对比
阿里使用的是:percona的XtraDB,XtraDB比innodb性能更好完全可以取代XtraDB
①执行时间长
②等待时间长
|--查询语句写的烂
|--索引失效
|--关联查询太多join(设计缺陷或不得已的需求)
|--服务器调优及各个参数设置(缓冲、线程池)
索引(index)是帮助mysql高效获得数据的数据结构。
是排好序的快速查找数据结构。
索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储在磁盘上
平常所说的索引,一般指B树(多路搜索索引,并不一定是二叉树)结构组织的索引,唯一索引默认都是B+树索引,除了B+树 还有哈希索引(hash index)
|--排序和查找,降低io消耗和cpu消耗
|--索引是一张表保存了主键和索引列,指向记录,是占用空间的
|--降低更新表的速度,插入和删除会重新建索引
|-- 最优秀的索引需要花时间去摸索
索引分类:唯一索引(unique)、普通索引(Normal)、组合索引、全文索引
索引方法分类:BTREE、HASH
建索引的参考公式:
使用EXPLAIN关键字可以模拟优化器执行SQL语句,从而知道MySQL是
如何处理你的SQL语句的。分析你的查询语句或是结构的性能瓶颈
(1)Explain+SQL语句
(2)执行计划包含的信息
(1) id:select查询的序列号,表示查询中执行select执行子句或操作表的顺序
①id相等的情况
表示执行顺序由上到下
例:
EXPLAIN SELECT
*
FROM
tb_item_desc t1,
tb_item_param_item t2,
tb_item t3
WHERE
t1.item_id = t2.item_id
AND t2.item_id = t3.id;
结果:
顺序是:表t2,表t1,表t3
②id不等
如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
先执行鸡蛋黄,后鸡蛋清,再是鸡蛋壳,像是小鸡孵化,先执行子查询
③id即相等,又不等
(2) select_type
(3) table
显示这一行的数据是关于哪张表的
(4) type
system:
const:
条件等于一个常 量值
eq_ref:
连表条件匹配,只有一条记录匹配
ref:
条件查询,匹配的有很多条
rang:
主键在一个范围内
index:
全索引查询
(4) possible_keys和key
例如:酒宴,应到多少人(possible_key),实到多少人(key)
下例中:因为查询的是两个字段的组合索引,整合匹配上索引,但是mysql优化器没有检测到查询单个字段可以用到写索引,所以possible_keys是null,key是组合索引
(5)key_len
(6)ref
(7)rows
每张表有多少行被优化器查询
(8)Extra
Using filesort
因为去掉了一层的索引,直接用二层的索引进行排序,所以mysql不知道索引的排法,所以重新进行文件内排序
Using temporary
同样是索引没有覆盖
原因解释:
第一次索引建立在category_id,comments,views上,因为第二个索引是rang类型,所以使得views这个索引失效,
第二次建立的索引是category_id和views,顺带着把常量category_id带上了
对于左右连接的表建索引技巧:
左连接,就把索引健在右表的相应字段上
右连接,就把索引健在左表相应的字段上
原因解释:
因为如果是左连接的话,左表的数据是确定的,右表是不确定的,索引应该在右表上建索引使得优化器更快的匹配数据。
和双表类似
(查询的列尽量都在索引列的覆盖范围之内)
(1)like 'abc%' 在后面加%号
(2)使用索引覆盖
都是常量,跟索引的顺序不同
总结:条件的顺序和索引的顺序不一样,但是查询的效果是一样的,但是最好要一样,因为mysql优化器会进行一次转换,转换成和索引顺序一样的。
类比mysql,建议用第一个循环的写法,因为第一个循环只需要5次IO操作,第二个循环需要1000次的IO操作
场景:如果order by 的索引没有用上,而进行了filesort的话,需调整参数进行优化
filesort的双路排序:
双路排序:是首先根据相应的条件取出相应的排序字段和可以直接定位行数据的行指针信息,然后在sort buffer 中进行排序,排完以后再次读取数据。
单路排序:是一次性取出满足条件行的所有字段,然后在sort buffer中进行排序。
二 优化order by
当无法避免排序操作时,又该如何来优化呢?很显然,优先选择第一种using index 的排序方式,在第一种方式无法满足的情况下,尽可能让 MySQL 选择使用第二种单路算法来进行排序。这样可以减少大量的随机IO操作,很大幅度地提高排序工作的效率。
1 加大 max_length_for_sort_data 参数的设置
在 MySQL 中,决定使用老式排序算法还是改进版排序算法是通过参数 max_length_for_ sort_data 来决定的。当所有返回字段的最大长度小于这个参数值时,MySQL 就会选择改进后的排序算法,反之,则选择老式的算法。所以,如果有充足的内存让MySQL 存放须要返回的非排序字段,就可以加大这个参数的值来让 MySQL 选择使用改进版的排序算法。
2 去掉不必要的返回字段
当内存不是很充裕时,不能简单地通过强行加大上面的参数来强迫 MySQL 去使用改进版的排序算法,否则可能会造成 MySQL 不得不将数据分成很多段,然后进行排序,这样可能会得不偿失。此时就须要去掉不必要的返回字段,让返回结果长度适应 max_length_for_sort_data 参数的限制。
3 增大 sort_buffer_size 参数设置
这个值如果过小的话,再加上你一次返回的条数过多,那么很可能就会分很多次进行排序,然后最后将每次的排序结果再串联起来,这样就会更慢,增大 sort_buffer_size 并不是为了让 MySQL选择改进版的排序算法,而是为了让MySQL尽量减少在排序过程中对须要排序的数据进行分段,因为分段会造成 MySQL 不得不使用临时表来进行交换排序。
但是这个值不是越大越好:
1 Sort_Buffer_Size 是一个connection级参数,在每个connection第一次需要使用这个buffer的时候,一次性分配设置的内存。
2 Sort_Buffer_Size 并不是越大越好,由于是connection级的参数,过大的设置+高并发可能会耗尽系统内存资源。
3 据说Sort_Buffer_Size 超过2M的时候,就会使用mmap() 而不是 malloc() 来进行内存分配,导致效率降低。
单路排序:
结论及引申:
(1)说明
(2)查看是否开启及如何开启
文件类型的配置:
(3)日志分析工具mysqldumpslow
1、批量插入数据脚本,插入1000w数据
(1)建表
#新建库
create database bigData;
use bigData;
#1建表dept
CREATE TABLE `dept` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`deptno` mediumint(9) NOT NULL,
`dname` varchar(20) NOT NULL,
`loc` varchar(13) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
#2建表emp
CREATE TABLE `emp` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`empno` mediumint(9) NOT NULL DEFAULT '0',
`ename` varchar(20) NOT NULL,
`job` varchar(9) NOT NULL,
`mgr` mediumint(9) NOT NULL,
`hiredate` date NOT NULL,
`sal` decimal(7,2) NOT NULL,
`comm` decimal(7,2) NOT NULL,
`deptno` mediumint(9) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
(2)首先设置log_bin_trust_function_creators的参数
-------------------------------------------------------------------------------------
MySQL参数log_bin_trust_function_creators介绍
简单介绍一下,当二进制日志启用后,这个变量就会启用。它控制是否可以信任存储函数创建者,不会创建写入二进制日志引起不安全事件的存储函数。如果设置为0(默认值),用户不得创建或修改存储函数,除非它们具有除CREATE ROUTINE或ALTER ROUTINE特权之外的SUPER权限。 设置为0还强制使用DETERMINISTIC特性或READS SQL DATA或NO SQL特性声明函数的限制。 如果变量设置为1,MySQL不会对创建存储函数实施这些限制。 此变量也适用于触发器的创建。
来源: https://www.cnblogs.com/kerrycode/p/7641835.html
设置办法:
-------------------------------------------------------------------------------------------
(3)创建函数,保证每条数据都不同
随机产生字符串
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
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 $$
CREATE FUNCTION rand_num() RETURNS INT(5)
BEGIN
DECLARE i INT DEFAULT 0;
SET i = FLOOR(100+RAND()*10);
RETURN i;
END $$
(4)创建存储过程
插入员工表的存储过程
delimiter $$
CREATE PROCEDURE inset_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 $$
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 ;
CALL insert_dept(100,10);
delimiter;
CALL insert_emp(100001,500000);
(1)show profile是什么?
可以分析出mysql执行的整个生命周期
(2)分析步骤
查看mysql是否支持
开始分析
配置全局查询日志(只在测试环境配,生产环境不要配)
配置启用
编码启用
加读锁:
加写锁:
表锁分析:
经常出现的异常:
行锁分析:
优化建议:
页锁:
介于表锁和行锁之间
①mysql的优化与执行
1、mysql会解析查询,并创建内部数据结构(解析树),然后对其进行各种优化,包括重写查询、决定表的读取顺序,以及选择合适的索引等。
2、用户可以通过特殊的关键字提示(hint)优化器,影像决策过程。或者explain优化过程的各个因素。
3、对于select,在解析查询之前,服务器会先查询缓存(Quey Cache),如果能够在其中找到对应的查询,就直接返回查询缓存中的结果集。
②mysql事物
非事物引擎:MyISAM
事物引擎:mysql自己的:innodb,NDB cluster
第三方:XtraDB,PBXT
-------------------简单的回滚操作------------
START TRANSACTION;
SELECT ccs FROM ccs;
UPDATE ccs
SET ccs = 4;
ROLLBACK;
COMMIT;
-------------------------------------------------------
---------------------查看自动提交是否打开------
SHOW VARIABLES LIKE 'autocommit';
on 代表打开
off 代表关闭
SET autocommit = off 关闭
SET autocommit = on 打开
mysql的行级锁:
行级锁又分共享锁和排他锁。
共享锁:
名词解释:共享锁又叫做读锁,所有的事务只能对其进行读操作不能写操作,加上共享锁后在事务结束之前其他事务只能再加共享锁,除此之外其他任何类型的锁都不能再加了。
用法:SELECT `id` FROM table WHERE id in(1,2) LOCK IN SHARE MODE 结果集的数据都会加共享锁
排他锁:
名词解释:若某个事物对某一行加上了排他锁,只能这个事务对其进行读写,在此事务结束之前,其他事务不能对其进行加任何锁,其他进程可以读取,不能进行写操作,需等待其释放。
用法:SELECT `id` FROM mk_user WHERE id=1 FOR UPDATE
来源: https://www.cnblogs.com/IT--Loding/p/6204093.html
mysql默认隔离级别
select @@tx_isolation;
可重复读
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交(read-uncommitted) | 是 | 是 | 是 |
不可重复读(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
注:要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。
注:需要注意的是,在事务中,只有SELECT ... FOR UPDATE 或LOCK IN SHARE MODE 同一笔数据时会等待其它事务结束后才执行,一般SELECT ... 则不受此影响
上面我们提到,使用select…for update会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认Row-Level Lock(行级锁),行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,所以只有「明确」地指定主键,MySQL 才会执行Row lock (只锁住被选取的数据) ,否则MySQL 将会执行Table Lock (将整个数据表单给锁住)。
set autocommit=0;
begin;
select * from table1 where getTime < 1 and IsSuccess=0 order by id asc limit 0,30 for update;
commit;
-- 2:
update table1 a set IsSuccess=0 where id =400000;
来源: https://www.cnblogs.com/Lawson/p/5008741.html
使用悲观锁会报错吗?
开始不报错,会一直处在查询阻塞中,会报锁等待超时
[Err] 1205 - Lock wait timeout exceeded; try restarting transaction
事务并发性,粗略的理解就是单位时间内能够执行的事务数量,常见的单位是 TPS( transactions per second).
那在数据量和业务操作量一定的情况下,常见的提高事务并发性主要考虑的有哪几点呢?
1.提高服务器的处理能力,让事务的处理时间变短。
这样不仅加快了这个事务的执行时间,也降低了其他等待该事务执行的事务执行时间。
2.尽量将事务涉及到的 sql 操作语句控制在合理范围,换句话说就是不要让一个事务包含的操作太多或者太少。
在业务繁忙情况下,如果单个事务操作的表或者行数据太多,其他的事务可能都在等待该事务 commit或者 rollback,这样会导致整体上的 TPS 降低。但是,如果每个 sql 语句都是一个事务也是不太现实的。一来,有些业务本身需要多个sql语句来构成一个事务(比如汇款这种多个表的操作);二来,每个 sql 都需要commit,如果在 mysql 里 innodb_flush_log_at_trx_commit=1 的情况下,会导致 redo log 的刷新过于频繁,也不利于整体事务数量的提高(IO限制也是需要考虑的重要因素)。
3.在操作的时候,尽量控制锁的粒度,能用小的锁粒度就尽量用锁的粒度,用完锁资源后要记得立即释放,避免后面的事务等待。
但是有些情况下,由于业务需要,或者为了保证数据的一致性的时候,必须要增加锁的粒度,这个时候就是下面所说的几种情况。
select col from t where where_clause for update 的目的是在执行这个 select 查询语句的时候,会将对应的索引访问条目进行上排他锁(X 锁),也就是说这个语句对应的锁就相当于update带来的效果。
那这种语法为什么会存在呢?肯定是有需要这种方式的存在啦!!请看下面的案例描述:
案例1:
前提条件:
mysql 隔离级别 repeatable-read ,
事务1:
建表: CREATE TABLE `lockt` ( `id` int(11) NOT NULL, `col1` int(11) DEFAULT NULL, `col2` int(11) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `col1_ind` (`col1`), KEY `col2_ind` (`col2`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 插入数据 。。。。。 mysql> set autocommit=0; Query OK, 0 rows affected (0.00 sec) mysql> select * from lockt; +----+------+------+ | id | col1 | col2 | +----+------+------+ | 1 | 1 | 1 | | 2 | 2 | 3 | | 5 | 5 | 5 | | 6 | 6 | 9 | | 7 | 7 | 14 | | 8 | 8 | 20 | +----+------+------+ 6 rows in set (0.00 sec)
然后另外一个事务2 进行了下面的操作:
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from lockt; +----+------+------+ | id | col1 | col2 | +----+------+------+ | 1 | 1 | 1 | | 2 | 2 | 3 | | 5 | 5 | 5 | | 6 | 6 | 9 | | 7 | 7 | 14 | | 8 | 8 | 20 | +----+------+------+ 6 rows in set (0.00 sec) mysql> update lockt set col2= 144 where col2=14; Query OK, 1 row affected (0.01 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> commit; Query OK, 0 rows affected (0.00 sec)
结果:可以看到事务2 将col2=14 的列改为了 col2=144.
可是事务1继续执行的时候根本没有觉察到 lockt 发生了变化,请看 事务1 继续后面的操作:
mysql> select * from lockt; +----+------+------+ | id | col1 | col2 | +----+------+------+ | 1 | 1 | 1 | | 2 | 2 | 3 | | 5 | 5 | 5 | | 6 | 6 | 9 | | 7 | 7 | 14 | | 8 | 8 | 20 | +----+------+------+ 6 rows in set (0.01 sec) mysql> update lockt set col2=col2*2 where col2=14; Query OK, 0 rows affected (0.00 sec) Rows matched: 0 Changed: 0 Warnings: 0 mysql> commit; Query OK, 0 rows affected (0.00 sec) mysql> select * from lockt; +----+------+------+ | id | col1 | col2 | +----+------+------+ | 1 | 1 | 1 | | 2 | 2 | 3 | | 5 | 5 | 5 | | 6 | 6 | 9 | | 7 | 7 | 144 | | 8 | 8 | 20 | +----+------+------+ 6 rows in set (0.00 sec)
结果: 事务1 明明查看到的存在 col2=12 的行数据,可是 update 后,竟然不仅没有改为他想要的col2=28 的值,反而变成了 col2=144 !!!!
这在有些业务情况下是不允许的,因为有些业务希望我通过 select * from lockt; 查询到的数据是此时数据库里面真正存储的最新数据,并且不允许其他的事务来修改只允许我来修改。(这个要求很霸气,但是我喜欢。。)
这种情况就是很牛逼的情况了。具体的细节请参考下面的案例2:
案例2:
mysql 条件和案例1 一样。
事务1操作:
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from lockt where col2=20 for update; +----+------+------+ | id | col1 | col2 | +----+------+------+ | 8 | 8 | 20 | +----+------+------+ 1 row in set (0.00 sec)
事务2 操作:
mysql> select * from lockt; +----+------+------+ | id | col1 | col2 | +----+------+------+ | 1 | 1 | 1 | | 2 | 2 | 3 | | 5 | 5 | 5 | | 6 | 6 | 9 | | 7 | 7 | 144 | | 8 | 8 | 20 | +----+------+------+ 6 rows in set (0.00 sec) mysql> update lockt set col2=222 where col2=20;
注意: 事务2 在执行 update lockt set col2=222 where col2=20; 的时候,会发现 sql 语句被 block住了,为什么会发现这种情况呢?
因为事务1 的 select * from lockt where col2=20 for update; 语句会将 col2=20 这个索引的入口给锁住了,(其实有些时候是范围的索引条目也被锁住了,暂时不讨论。),那么事务2虽然看到了所有的数据,但是想去修改 col2=20 的行数据的时候, 事务1 只能说 “不可能也不允许”。
后面只有事务1 commit或者rollback 以后,事务2 的才能够修改 col2=20 的这个行数据。
总结:
这就是 select for update 的使用场景,为了避免自己看到的数据并不是数据库存储的最新数据并且看到的数据只能由自己修改,需要用 for update 来限制。
如果看了前面的 select *** for update ,就可以很好的理解 select lock in share mode ,in share mode 子句的作用就是将查找到的数据加上一个 share 锁,这个就是表示其他的事务只能对这些数据进行简单的select 操作,并不能够进行 DML 操作。
那它和 for update 在引用场景上究竟有什么实质上的区别呢?
lock in share mode 没有 for update 那么霸道,所以它有时候也会遇到问题,请看案例3
案例3:
mysql 环境和案例1 类似
事务1:
mysql> select * from lockt; +----+------+------+ | id | col1 | col2 | +----+------+------+ | 1 | 1 | 1 | | 2 | 2 | 3 | | 5 | 5 | 5 | | 6 | 6 | 9 | | 7 | 7 | 144 | | 8 | 8 | 20 | +----+------+------+ 6 rows in set (0.00 sec) mysql> select * from lockt where col2=20 lock in share mode; +----+------+------+ | id | col1 | col2 | +----+------+------+ | 8 | 8 | 20 | +----+------+------+ 1 row in set (0.00 sec)
事务2 接着开始操作
mysql> select * from lockt; +----+------+------+ | id | col1 | col2 | +----+------+------+ | 1 | 1 | 1 | | 2 | 2 | 3 | | 5 | 5 | 5 | | 6 | 6 | 9 | | 7 | 7 | 144 | | 8 | 8 | 20 | +----+------+------+ 6 rows in set (0.00 sec) mysql> select * from lockt where col2=20 lock in share mode; +----+------+------+ | id | col1 | col2 | +----+------+------+ | 8 | 8 | 20 | +----+------+------+ 1 row in set (0.01 sec)
后面的比较蛋疼的一幕出现了,当 事务1 想更新 col2=20 的时候,他发现 block 住了。
mysql> update lockt set col2=22 where col2=20;
解释:因为事务1 和事务2 都对该行上了一个 share 锁,事务1 以为就只有自己一个人上了 S 锁,所以当事务一想修改的时候发现没法修改,这种情况下,事务1 需要使用 for update 子句来进行约束了,而不是使用 for share 来使用。
使用情景:
1. select *** for update 的使用场景
为了让自己查到的数据确保是最新数据,并且查到后的数据只允许自己来修改的时候,需要用到 for update 子句。
2. select *** lock in share mode 使用场景
为了确保自己查到的数据没有被其他的事务正在修改,也就是说确保查到的数据是最新的数据,并且不允许其他人来修改数据。但是自己不一定能够修改数据,因为有可能其他的事务也对这些数据 使用了 in share mode 的方式上了 S 锁。
性能影响:
select for update 语句,相当于一个 update 语句。在业务繁忙的情况下,如果事务没有及时的commit或者rollback 可能会造成其他事务长时间的等待,从而影响数据库的并发使用效率。
select lock in share mode 语句是一个给查找的数据上一个共享锁(S 锁)的功能,它允许其他的事务也对该数据上 S锁,但是不能够允许对该数据进行修改。如果不及时的commit 或者rollback 也可能会造成大量的事务等待。
for update 和 lock in share mode 的区别:前一个上的是排他锁(X 锁),一旦一个事务获取了这个锁,其他的事务是没法在这些数据上执行 for update ;后一个是共享锁,多个事务可以同时的对相同数据执行 lock in share mode。