其实存储引擎以前叫做表处理器,后来可能⼈们觉得太土,就改成了存储引擎的叫法。存储引擎是基于表的,所以也可被称为表类型。查询当前数据库支持的存储引擎:show engines;
存储引擎微观结构:包括数据内存区域和日志内存区域。
数据内存区域:
日志内存区域,负责Redo日志缓冲。
InnoDB是一种兼顾高可靠性和高性能的通用存储引擎,在MySQL 5.5版本之后,被设置为默认的MySQL存储引擎。
table.ibd:InnoDB引擎的每张表都会对应这样一个表空间文件,存储该表的表结构、索引和数据。
系统变量innodb_file_per_table:show variables like 'innodb_file_per_table';
查看table.idb文件:ibd2sdi sex.ibd,ibd2sdi命令从ibd文件中提取sdi表结构数据,MySQL数据存放目录:C:\ProgramData\MySQL\MySQL Server 8.0\Data\test。
逻辑存储结构:
表空间概念是引入于Oracle数据库,起初是为了解决存储空间扩展的问题。
MySQL表空间类型
表空间管理:
select @@innodb_file_per_table;,1代表独立表空间,0代表共享表空间set global innodb_file_per_table=0;,重新登录会话查看。修改完之后,只影响新创建的表扩展共享表空间大小和个数:
表空间迁移:
跳过外键检查:set foreign_key_checks=0;
innodb_flush_method:控制刷写磁盘时,是否使用OS Cache:
生产建议使用O_DIRECT,最好是配合固态盘使用。
innodb_buffer_pool_size:数据缓冲区总大小。作用是缓冲数据页和索引页,MySQL最大的内存区域。默认128M,官方建议80-90%物理内存,生产建议75%以下,按需调配。
MyISAM是MySQL早期的默认存储引擎,特点:
文件:
MyISAM的回表操作是十分快速的,因为是拿着地址偏移量直接到文件中取数据,反观InnoDB是通过获取主键之后再去聚簇索引里找记录,虽然说也不慢,但还是比不上直接用地址去访问。
InnoDB和MyISAM三大区别:InnoDB支持事务安全、支持行锁、支持外键。MyISAM支持表锁。
该引擎的表数据是存储在内存中的,只有一个文件xxx.sdi用来存储表结构信息。
按照数据结构分类:
按照物理存储分类:
聚集索引选取规则:
按照字段特性分类:
分类含义特点关键字主键索引针对表中主键创建的索引默认自动创建,只能有一个primary唯一索引避免同一表中某数据列中的值重复可以有多个unique常规索引快速定位特定数据可以有多个全文索引查找的是文本中的关键词,而不是比较索引中的值可以有多个fulltext 按照字段个数分类:单列索引、联合索引(也叫复合索引、组合索引)
索引是在引擎层实现的,不同的存储引擎有不同的结构,主要包含以下几种:
索引InnoDBMyISAMMemoryB+Tree✅✅✅Hash❌❌✅R-Tree❌✅❌Full-text5.6版本之后支持✅❌
二叉树缺点:顺序插入时,会形成一个链表,查询性能大大降低。大数据量情况下,层级较深,检索速度慢。
红黑树缺点:大数据量情况下,层级较深,检索速度慢。
B Trees(多路平衡查找树):以一颗最大度数为5的B Trees为例,每个节点最多存储4个key,5个指针。具体动态变化过程可参考:https://www.cs.usfca.edu/~galles/visualization/BTree.html
B+ Trees:MySQL索引数据结构对经典的B Trees进行了优化。在原B Trees的基础上,增加一个指向相邻叶子节点的链表指针,就形成了带有顺序指针的B+ Trees,提高区间访问的性能,这样很适合范围查询。B+树的非叶子节点上只有索引值,没有数据,所以非叶子节点可以存储更多的索引值,这样让B+树更矮更胖,提高检索效率。
Hash:
🤔为什么InnoDB存储引擎选择使用B+ Trees索引结构?
回表查询:是指先走二级索引,找到对应的主键值,再根据主键值再到聚集索引当中,拿到这一行的数据。
🤔InnoDB主键索引的B+ Trees高度为多高呢? 假设:一行数据大小为1k,一页中可以存储16行这样的数据。InnoDB指针占用6个字节的空间,主键即使为bigint,占用字节数为8。
高度为2:
高度为3:1171 * 1171 * 16 = 21939856
会发现,即使存储2000+万的记录,树的结构也才只有3层。如果说存储了4000+万的记录,那就有可能超过3层,这个时候就得考虑分库分表了。
MySQL中建议索引树高度为3-4层。
🤔如果一张表没有主键索引,那还会创建B+树吗? 当一张表没有主键索引时,默认会使用一个隐藏的内置聚集索引(clustered index)。这个聚集索引是基于表的物理存储顺序构建,通常是使用B+树来实现的。
查看索引:show index from 表;
删除索引:drop index 索引名 on 表;
创建索引:
压力测试:mysqlslap --concurrency=100 --number-of-queries=200 --auto-generate-sql;
如果索引了多列(联合索引),要遵守最左前缀法则。最左前缀法则指的是查询从索引的最左列开始(即最左边的列必须存在),并且不跳过索引中的列。如果跳跃某一列,索引将部分失效(后面的字段索引失效)。
范围查询:联合索引中,出现范围查询(>, <),范围查询右侧的列索引失效。
索引列运算:不要在索引列上进行运算操作,索引将失效。
字符串不加引号:字符串类型字段使用时,不加引号,索引将失效。
模糊匹配:如果仅仅是尾部模糊匹配,索引不会失效。如果是头部模糊匹配,索引失效。
or连接的条件:用or分割开的条件,如果or前条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到。
数据分布影响:如果MySQL评估使用索引比全表更慢,则不使用索引。
SQL提示,是优化数据库的一个重要手段,简单来说,就是在SQL语句中加入一些人为的提示来达到优化操作的目的。
use index:select * from user use index(idx_name) where name='张三';
ignore index:select * from user ignore index(idx_name) where name='张三';
force index:select * from user force index(idx_name) where name='张三';
尽量使用覆盖索引(查询使用了索引,并且需要返回的列在该索引中已经全部能够找到),减少select *。
当字段类型为字符串(varchar,text等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时浪费大量的磁盘IO,影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率。
语法:create index idx_name on user(name(n));
前缀长度:可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表记录总数的比值,索引选择性越高则查询效率越高,唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
select count(distinct name) / count(*) from user; select count(distinct substring(name,1,3)) / count(*) from user;
查看服务器状态信息:show global status like 'Com_______';
可参考MySQL日志管理
查看是否支持profile操作:select @@have_profiling;
查看profiling开关状态:select @@profiling;
默认profiling是关闭的,开启语句为:set profiling=1;
查看每条SQL语句的耗时基本情况:show profiles;
查看指定query_id的SQL语句各个阶段耗时情况:show profile for query query_id;
查看指定query_id的SQL语句CPU使用情况:show profile cpu for query query_id;
直接在select语句之前加上关键字explain:explain select * from user;
各字段含义: id:SELECT查询的序列号,表示查询中执行select子句或者是操作表的顺序。id相同,执行顺序从上到下;id不同,值越大,越先执行。
select_type:表示SELECT的类型,常见的取值有SIMPLE(简单表,即不使用表连接或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION中的第二个或者后面的查询语句)、SUBQUERY(SELECT/WHERE之后包含了子查询)等。
type:表示连接类型,性能由好到差的连接类型为NULL、system、const(聚簇索引等值查询)、eq_ref(多表连接中,非驱动表连接条件是主键或唯一键)、ref(辅助索引等值查询)、range(索引范围查询)、index(全索引扫描)、all(全表扫描)。
possible_key:表示可能应用在这张表上的索引,一个或多个。
key:实际使用的索引,如果为NULL,则没有使用索引。
key_len:表示索引中使用的字节数,该值为索引字段最大可能长度,并非实际使用长度,在不损失精确性的前提下,长度越短越好。
rows:表示根据表统计信息及索引选用情况,大致估算出找到所需记录需要读取的行数。
Extra:表示不适合在其他字段中显示,但是十分重要的额外信息。
insert优化
大批量插入数据,使用insert性能较低,此时可以使用load指令进行插入。
# 客户端连接服务端时,加上参数--local-infile mysql --local-infile -uroot -p # 设置全局参数为1,开启从本地加载文件导入数据的开关 set global local_infile=1; # 执行load指令将数据加载到表结构中 load data local infile '文件路径' into table '表名' fields terminated by ',' lines terminated by '\n';
主键设计原则:
根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则。
尽量使用覆盖索引,避免使用select *。
多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)。
如果不可避免的出现filesort,大数据量排序时,可以适当增大排序缓冲区大小sort_buffer_size(默认256K)。
创建索引指定升降顺序:create index idx_id_name_ad on user(id asc, name desc);
类似order by优化。
优化思路:一般分页查询时,通过创建 覆盖索引 能够比较好地提高性能,可以通过覆盖索引加子查询形式进行优化。
按照效率排序:count(字段) < count(主键) < count(1) < count(*),所以尽量使用count(*)。
InnoDB的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则会从行锁升级为表锁。
每次数据库启动,会自动在内存中生成information_schema视图,用于查询MySQL部分元数据信息。
数据库资产统计,统计每个库,所有表个数、表名:select table_schema, count(table_name), group_concat(table_name) from information_schema.tables group by table_schema \G
统计每个库占用空间总大小:select table_schema, sum(AVG_ROW_LENGTH*TABLE_ROWS+INDEX_LENGTH) from information_schema.tables group by table_schema;
查询业务数据库(系统库除外),所有非InnoDB表:select table_schema, table_name from information_schema.tables where engine!='InnoDB' and table_schema not in ('sys','performance_schema','information_schema','mysql');
查询业务数据库(系统库除外),所有非InnoDB表,并将非InnoDB表转换为InnoDB表:
[mysqld]下添加secure_file_priv=/tmpselect concat('alter table ',table_schema,'.',table_name,' engine=innodb;') from information_schema.tables where engine!='InnoDB' and table_schema not in ('sys','performance_schema','information_schema','mysql') into outfile '/tmp/alter.sql';;mysql </tmp/alter.sql创建视图:create or replace view user_v as select * from user;
查询视图:show create view user_v;
修改视图:
删除视图:drop view if exists user_v;
当使用with check option子句创建视图时,MySOL会通过视图检查正在更改的每个行,例如 插入,更新,删除,以使其符合视图的定义。MySQL允许基于另一个视图创建视图,它还会检查依赖视图中的规则以保持一致性。为了确定检查的范围,MySOL提供了两个选项:cascaded和local,默认值为cascaded。
create or replace view user_v as select * from user where id>1 with cascaded check option; create or replace view user_v as select * from user where id>1 with local check option;
要使视图可更新,视图中的行与基础表中的行之间必须存在一对一的关系。如果视图包含以下任何一项,则该视图不可更新:
作用:
在命令行中,执行创建存储过程的SQL时,需要通过关键字delimiter指定SQL语句的结束符。
创建:
mysql> delimiter $$ mysql> create procedure p() -> begin -> select count(*) from user; -> end -> $$ mysql> delimiter ;
调用:call p();
查看指定数据库的存储过程及状态信息:select * from information_schema.routines where routine_schema='test';
查询某个存储过程的定义:show create procedure p;
删除:drop procedure if exists p;
系统变量是MySQL服务器提供,不是用户定义的,属于服务器层面。分为全局变量(GLOBAL)、会话变量(SESSION)。
查看所有系统变量(默认是session):show variables;
模糊匹配查找变量:show global variables like 'auto%';
查看指定变量的值:
设置系统变量:
如果没有指定session/global,默认是session。MySQL服务重新启动之后,所设的全局参数会失效,要想不失效,可以在/etc/my.cnf中配置。
用户定义的变量无需对其进行声明或初始化,只不过获取到的值为NULL。用户自定义的变量,只在当前会话有效,以@开头用于存储用户定义的变量。
赋值:
select count(*) into @mycount from user;
在MySQL中,赋值推荐使用 :=。
使用:select @myname, @name1, @name2;
局部变量是根据需要定义的在局部生效的变量,访问之前,需要declare声明。可用作存储过程内的局部变量和输入参数,局部变量的范围是在其内声明的begin...end块。
mysql> delimiter $$ mysql> create procedure p() -> begin -> declare my_count int default 0; -> select count(*) into my_count from user; -> select my_count; -> end -> $$ mysql> delimiter ;
in:输入参数
out:输出参数
mysql> delimiter mysql> delimiter ; mysql> call p1(82, @result); mysql> select @result;
inout:输入输出参数
mysql> delimiter $$ mysql> create procedure p2(inout score double) -> begin -> set score=score*0.5; -> end -> $$ mysql> delimiter ; mysql> set @score=80; mysql> call p2(@score); mysql> select @score;
if 条件1 then ...; elseif 条件2 then ...; else ...; end if; case 值 when 值1 then ... when 值2 then ...; else ...; end case; case when 条件1 then ...; when 条件2 then ...; else ...; end case;
while 条件 do ...; end while;
repeat ...; until 条件 end repeat;
计算从1到n之间偶数累加值:
mysql> delimiter $$ mysql> create procedure p3(in n int) -> begin -> declare total int default 0; -> sum:loop -> if n<=0 then leave sum; -> end if; -> if n%2=1 then -> set n:=n-1; -> iterate sum; -> end if; -> set total:=total+n; -> set n:=n-1; -> end loop sum; -> select total; -> end -> $$ mysql> delimiter ; mysql> call p3(10);
游标(cursor)是用来存储查询结果集的数据类型,在存储过程和函数中可以使用游标对结果集进行循环的处理。游标的使用包括游标的声明、open、fetch 和 close。
声明游标:declare u_cursor for select * from user;
打开游标:open u_cursor;
获取游标:fetch u_cursor into 变量;
关闭游标:close u_cursor;
条件处理程序(Handler)可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤。
handler_action:continue: 继续执行当前程序;exit: 终止执行当前程序。
condition_value
存储函数是有返回值的存储过程,存储函数的参数只能是IN类型的。
mysql> delimiter $$ mysql> create function f1(n int) -> returns int deterministic -> begin -> declare total int default 0; -> while n>0 do -> set total=total+n; -> set n=n-1; -> end while; -> return total; -> end -> $$ mysql> delimiter ; mysql> select f1(100);
创建:create trigger insert_trigger after insert on 表 for each row; begin ... end
查看:show triggers;
删除:drop trigger if exists 触发器名;
锁是属于资源的,不是某个事务的特性。每次事务需要资源的时候,需要申请持有资源的锁。
锁类型:
内存锁:Latch是轻量级的锁,因为其要求锁定的时间必须非常短。若持续时间长,则应用性能会非常差,在InnoDB引擎中,Latch又可以分为mutex互斥量和rwlock读写锁
对象锁:
MDL元数据锁:修改元数据时、备份时
Table Lock表锁
Record Lock行锁
Gap Lock间隙锁
Next-Key Lock临键锁
功能分类:
加上一个全局锁:flush tables with read lock;
数据备份:mysqldump -uroot -p123456 test > D:/test.sql
在InnoDB引擎中,可以在备份时加上--single-transaction参数来完成不加锁的一致性数据备份。
mysqldump --single-transaction -uroot -p123456 test > D:/test.sql
释放锁:unlock tables;
备份指定数据库表结构:mysqldump -uroot -B test --no-data > test.sql
加锁:lock tables 表名 read/write;
释放锁:unlock tables;
读锁不会阻塞其他客户端的读,但是会阻塞写。写锁既会阻塞其他客户端的读,又会阻塞其他客户端的写。
MDL(meta data lock)加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。MDL锁主要作用是维护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作。为了避免DML与DDL冲突,保证读写的正确性。
在MySQL5.5中引入了MDL,当对一张表进行增删改查的时候,加MDL读锁(共享);当对表结构进行变更操作的时候,加MDL写锁(排他)。
为了避免DML在执行时,加的行锁与表锁的冲突,在lnnoDB中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。
每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。
InnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。对于行级锁,主要分为以下三类:
行锁(Record Lock):锁定单个行记录的锁,防止其他事务对此行进行update和delete。
间隙锁(Gap Lock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其事务在这个间隙进行inset,产生幻读。
临键锁(Next-Key Lock):行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap。
看有没有锁等待:SHOW STATUS LIKE 'innodb_row_lock%';
查看哪个事务在等待(被阻塞):SELECT * FROM information_schema.INNODB_TRX WHERE trx_state='LOCK WAIT';
查看锁源:SELECT * FROM sys.innodb_lock_waits;
找到锁源thread_id:SELECT * FROM performance_schema.threads WHERE processlist_id=15;
找到锁源SQL语句:
死锁监控:
show engine innodb status \G show variables like '%deadlock%';
把死锁信息打印进错误日志:vi /etc/my.cnf,添加innodb_print_all_deadlocks = 1。
本文作者:a
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!