存储引擎的分类
INNODB和myIsam的对比
INnoDb内存结构
Buffer Pool:缓冲池,简称BP。
Page管理机制
page根据状态可以分为三种类型:
针对上面三种类型,innodb通过三种链表结构来维护和管理
改进型LRU算法维护
Change Buffer:写缓冲区。
undo:?undo意思为撤销和回退,以撤销为目的,指能回到过去的某种状态。
undo log:?事务在开始之前,会将历史的数据保存到undo log中,当发生回滚或者未提交事务时数据库崩溃了,可以通过undo log回退到数据的快照版本,撤销掉未提交的事务对数据库产生的影响。
undo log的产生和销毁:?undo log在事务开始之前产生,在事务结束之后也不会立即销毁,而是先将undo log加入到删除列表中,而是通过后台的线程purage thread进行销毁;uodo log的本质是逻辑日志,是记录的一个相反的过程,例如:如果执行的是一个delete操作,则在undo log中记录一条insert操作,执行的是一个insert操作,则在undo log日志中记录一条delete操作,如果是执行的一个update 操作,则在undo log中记录一条相反的update记录。
undo log在MVCC(多版本并发控制)中的应用:?事务提交之前,undo log中先保存了旧版本的数据,此时事务没结束时,可以供其他的事务进行快照读。 (理解:事务A执行更新操作,首先先把旧版本数据同步到 undo buffer中(后续会持久化道undo log中),事务B手动开启事务,执行查询操作,会在undo log中读取快照版本数据)
redo:?redo的含义就是重做,意思是恢复操作,在数据库发生意外时重现操作。
redo log:?事务修改的任意数据,都将最新的数据备份到redo log中,称为重做日志。
redo log的生成和释放:随着事务的操作的执行,就会产生redo log,随着事务的提交,会将生产的redo log写到log buffer中,并不是随着事务的结束log buffer中的数据会立即写入到磁盘中。等到buffer pool中的脏页刷新到磁盘中之后,redo log的使命也就完成了,redo log占用的空间就可以被重用(覆盖写入)。
redo log的实现原理:?redo log是为了事务的持久化这一特性的产物,当事务提交时,buffer pool里面的脏页还没有全部的刷新到硬盘时,数据库宕机了,当重启mysql之后,就可以依据redo log里面的内容进行重做,继续将数据持久化到硬盘中。
redo log写入机制:?redo log的写入机制是顺序写入的方式,写满时则进行回溯,进行覆盖写。
普通索引:即针对数据库表创建索引
唯一索引:与普通索引类似,添加唯一索引的列值必须唯一,但允许有空值
主键索引:特殊的唯一索引,不允许有空值,一般是在建表的时候同时创建主键索引。
组合索引: 为了进一步的榨取mysql的效率,考虑建立组合索引,即将数据库表中的多个字段联合起来作为一个组合索引。
全文索引:
用于快速查找记录的一种数据结构。需要额外的开辟空间和数据维护工作。
二分查找法
二分查找法也叫作折半查找法,时间复杂度是log2 N,二分查找法的优点是等值查询和范围查询的效率比较高,缺点是对于一些增删改的操作比较慢。
查询过程:
Hash结构
B+Tree结构
B树的阶数指的是每个节点最多有几个子节点(N阶指的是有N个子节点)
每个节点里面最多存放N-1个值
B-Tree结构
B+Tree结构
聚簇索引和辅助索引
最常见的查询类型就是simple,表示我们没有使用子查询和联合查询
# 使用sql案例:
# SELECT_type = SIMPLE
EXPLAIN SELECT * FROM `lagou_auth_code`;
# SELECT_type = PRIMART 和 SUBQUERY
EXPLAIN select * from lagou_auth_code where create_time = (SELECT MAX(create_time) from lagou_auth_code);
EXPLAIN SELECT a.code,
(SELECT email from lagou_token where token = 'b53e8b9c-04a2-4b3e-8306-e5a50a8c2c85') temail
FROM lagou_auth_code a ;
# SELECT_type = PRIMART 和 DEPENDENT SUBQUERY
explain SELECT email,`code`
from lagou_auth_code l1
where create_time = (
SELECT MAX(create_time) from lagou_auth_code l2 where l1.email = l2.email);
# SELECT_type = PRIMART 和 UNION 和 UNION RESULT
EXPLAIN SELECT code
from lagou_auth_code where email = 'zae_zangchuanlei@163.com'
UNION
SELECT token
from lagou_token where email = 'zae_zangchuanlei@163.com';
# SELECT_type = PRIMART 和 DEPENDENT SUBQUERY 和 DEPENDENT UNION
EXPLAIN
SELECT * from lagou_auth_code
where id in (
SELECT id
from lagou_auth_code
where id = '394017376523259904'
UNION
all
SELECT id
from lagou_auth_code)
table:表示哪张表
partitios
type:连接类型(数据库引擎以什么方式去查询出数据,是比较重要的属性,根据这个属性可以判断出是全局扫描还是基于索引的扫描查询),常用属性值如下,自上至下效率越来越高。
ALL:表示全表扫描,性能最差。
INDEX:表示基于索引的全局扫描,先扫描索引再扫描全表数据。(在排序的情况下效率较高)
range:表示使用索引范围查询,使用>、>=,<,<=,in等等
ref:表示使用非唯一索引进行单值查询。
eq_ref:一般情况下出现在多表join查询中,表示前面的那个表每一个记录,都只能匹配后面表的一行结果(表设计时采用的一对一的方式)
const:表示我们使用主键或唯一索引做等值查询了,也被称作常量查询。
NULL:表示不用访问表,速度最快。
possible_keys:表示的是查询时能够使用到的索引。注意并不一定会真正的使用。
key:表示查询时真正使用的索引,显示的是索引名称。
key_len:表示查询使用索引的字节数量,可以用它来判断是否全部使用了组合索引,或者只用到索引的最左不分的部分字段值。
key_len的计算规则如下:
ref
rows:记录行数(mysql查询优化器会根据统计信息,估算SQL要查询到结果需要扫描多少行记录。原则上rows是越少效果越好)
filtered
extra:额外的扩展的一些信息。表示很多额外的信息,各种操作会在extra提示相关信息,常见的几种如下:
通过索引查询主键值,然后再去聚簇索引查询记录信息
只需要在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快,这就叫做索引覆盖。
复合索引遵循最左前缀原则,最左前缀顾名思义就是最左优先,即查询中条件使用到最左边的列,那么索引将会生效,如果查询使用第二列或者其他非最左边的列,那么索引将会失效。
面试题:mysql在使用like查询的时候,索引能不能起作用?
回答:mysql在使用的时候是可以被使用的,但是有条件,只有把%字符写在后面才会使用到索引。“target%”。
面试题:如果mysql表的某一列含有NULL值,那么包含该列的索引是否有效?
mysql可以在含有NULL的列上使用索引的,但是NULL和其他数据还是有区别的,不建议列上允许为NULL。最好设置NOT NULL,并给一个默认值。(NULL列需要增加额外的空间去记录其值是否为NULL)
查询是否使用了索引,只表示一个sql的执行过程,并不代表这个sql的执行效率,列入一个sql中where id = 1,这个就使用到了主键索引,而且是单值查询,此时效率就会高,但是如果where id > 0,即使存在主键索引,但由于还是范围查找,全表臊面,效率也还是没有提高。而慢查询日志关注的是sql的执行时间,一个sql一旦执行时间超过了指定的阈值,那么就会出现在慢查询日志中,即使一个sql使用了索引,但是它的效率不高执行不快的话,也是会出现在慢查询日志中。
我们在关注索引时,不要只关注到是否使用了,还要关注索引是否减少了扫描表的行数,如果扫描行数少了,那么效率才会得到提升。对于一个大表来讲,不仅仅要去创建索引,还是提高过滤性。过滤性好,执行效率才会高。
事务并发处理的一些问题:
完全顺序的执行所有事务的数据库操作,不需要加锁,简单的说就是全局排序。序列化的执行所有的事务单元,数据库的某一时刻只会执行某一个事务,符合强一致性,但是执行效率不高,处理性能低。
支持并发的处理事务,如果两个事务处理中涉及到同一块的数据时,则会触发排他锁,或者叫互斥锁,先进入的事务会独占资源,其他的事务进入等到的一个状态,等它处理完后释放锁,另一个事务才会进入。
排他锁和排队的区别在于:排队是对于整个数据库操作而言,每次只要一个事务在进行,而排他锁使用时,可以支持多事务操作数据库处理不同的表数据,只有发生事务冲突(也就是两个事务操作同一块的表数据时),才会触发互斥锁,导致其他事务堵塞等待。【可以参考生活中上厕所的案例:排队就是卫生间每次只能有一个人,排他锁就是多坑位可以允许多个人,只有当坑位不够时,会导致其他人等待】
读写锁可以让读和读的操作并行,读写,写读,写写还是要加排他锁。
使用的是copy on write的思想,除了支持读和读的并行,还支持读和写,写和读的并行,但是仍然不能保证写和写的并行。
MVCC被称为多版本控制,是很巧妙的产生多个版本,让事务可以看到自己应该可以看到的数据版本,将稀缺的独占资源互斥转化为并发,大大提高了系统的吞吐量和执行效率。
如何生成多版本?:每次事务操作之前,都会在undo日志中记录修改之前的数据状态和事务号,该备份记录可以用于其他事务的读取,也可以进行必要时的数据回滚。
快照读:读取的是记录的快照版本(可能是历史版本)不用加锁。
(理解:事务A修改了某条数据,但此时还未提交事务,事务B要读取这条数据,通过undo log拿到历史版本的数据,进行读取)
当前读:读取的是当前最新的版本,保证读的是当前返回的信息,需要加锁,让其他事务不会并发的修改这条记录。
(理解:事务A修改了某条数据,然后事务A在接下来的操作又读取这条数据,读取到的就是最新的修改的数据。)
MVCC的过程原理
数据库的隔离级别越高,并发问题就越小,但是并发处理能力越差(代价)。
事务隔离级别,针对innodb支持事务的引擎。
事务隔离级别和锁的关系
mysql的默认隔离级别:可重复读
oracle的默认隔离级别是:读已提交
显示隔离级别:show variables like 'tx_isolation'
select @@tx_isolation
悲观锁,是指在数据处理的过程中国,将数据处于锁定状态,一般使用数据库的锁机制实现。从广义上来看,行锁,表锁,读锁,写锁,共享锁,排他锁,都属于悲观锁的范畴。
lock table 表名 read;
,查看表加过的锁:show open tables
,删除表锁:unlock tables
)
select * from where deptno=1 lock in share mode
】总结:事务使用了共享锁,只能读取,不能修改,其他事务可以重复加读锁。select * from where deptno=1 for update
】总结:事务使用了排他锁,当前事务可以读取和修改,但是其他的事务不能够读取和修改操作。行级锁的实现其实是依靠其对应的索引,所以说如果操作没用到索引的查询,那么会锁住全表记录。
悲观锁和乐观锁都可以解决事务写写并发,在应用中可以根据并发处理能力区分,比如对并发率要求高的选择乐观锁;对于并发率要求低的可以选择悲观锁。
表锁死锁:
产生原因:用户A访问A表,锁住了A表,然后又访问表B;另一个用户B访问B表,锁住了B表,然后又访问A。A和B用户互相等待对方释放锁,引发了死锁。
解决方案:调整程序逻辑,尽量按照相同的顺序执行。
行级锁死锁:
产生原因一:在事务中执行没有索引的条件的查询,引发了全表扫描,把行级锁上升为了全表记录锁定(等价于表锁),多个这样的事务发生之后,就容易产生了死锁和阻塞。
解决方案一:不要使用太多的连表查询,使用explain关键字分析sql,必要时添加索引。
产生原因二:两个事务分别想拿到对方持有的锁,互相等待,于是产生死锁。
解决方案二:1.同一个事务,尽可能的做到一次锁定所需要的所有资源。 2.按照id对资源进行排序,然后按照顺序进行处理。
共享锁转换为排他锁
产生原因
事务A:select * from dept where deptno = 1 lock in share mode;
// 共享锁1
update dept set dname = 'java' where deptno=1;
//排他锁,3 【由于B有一个排他锁在等待,所以也没法获取一个排他锁,引发了死锁】
事务B:update dept set dname='java' where deptno=1;
//由于A有共享锁,没发货区到排他锁,需要等待。 2
解决方案?1.不让用户重复点击,避免引发同时对同一条记录多次操作。2.使用乐观锁进行控制。
show engine innodb status
show status like 'innodb_row_lock%'
应用架构演变
主从复制用途
主从复制部署条件
主从复制的步骤
涉及到的线程
主从复制可能存在的问题
针对主从复制存在的问题想到的解决方案
主从数据库搭建过程
阶段一:准备过程
1.准备两台服务器:132和130,其中130作为master,132作为slave。
2.使用rpm 安装包。tar -xvf mysql安装包。
3.检查mariadb :rpm -qa|grep mariadb
4.如果存在mariadb的话将进行移除:rpm -e mariadb-libs-5.5.41-2.el7_0.x86_64 --nodeps,避免干扰
5.rpm -ivh mysql-community-common-5.7.28-1.el7.x86_64.rpm
6.rpm -ivh mysql-community-libs-5.7.28-1.el7.x86_64.rpm
7.rpm -ivh mysql-community-libs-compat-5.7.28-1.el7.x86_64.rpm
8.rpm -ivh mysql-community-client-5.7.28-1.el7.x86_64.rpm
9.rpm -ivh mysql-community-server-5.7.28-1.el7.x86_64.rpm
10.rpm -ivh mysql-community-devel-5.7.28-1.el7.x86_64.rpm
11.将mysql实例化:mysqld --initialize --user=mysql
12.cat /var/log/mysqld.log 查看密码
13.启动mysql服务:systemctl start mysqld.service
14.systemctl status mysqld.service 查看mysql服务的状态
15.mysql -u root -p 登录mysql set password=password('root')修改密码
16.systemctl stop iptables 将iptables关闭
17.systemctl stop firewalld 将防火墙关闭
18.systemctl disable firewalld.service 将防火墙禁掉,防止自己启动
阶段二:主从配置
1.进入到主库服务器 :cd etc -- 进入etc文件夹下
2.vi my.cnf 【开bin_log功能:log_bin=mysql-bin server-id=1 sync-binlog=1(开启同步,每次执行写入性操作都与磁盘同步) binlog-ignore-db=information_schema(忽略指定的库不再同步,可以配置多个)】
3.systemctl reatart mysqld 重启mysql服务
4.开启mysql 授权,先进入mysql主库。然后执行命令:grant replication slave on?.?to 'root'@'%' identified by 'root'; 授权
5.grant all privileges on?.?to 'root'@'%' identified by 'root';
6.flush privileges;刷新授权操作
7.show master status;查看数据库的状态
8.进入从库的配置。同样修改my.cof 【server-id=2 relay_log=mysql-relay-bin read_only=1】
9.重新启动从库,使修改的配置生效。
10.查看从库的状态:show slave status;
11.change master to master_host='192.168.95.130',master_port=3306,master_user='root',master_password='root',master_log_file='mysql-bin.000002',master_log_pos=869;//设置与master的连接命令,针对binlog文件;
12.对主库进行一些操作,看一下从库有没有记录数据。
13.追加从库的话,需要把历史数据给同步过去:
mysqldump --all-databases > mysql_backup_all.sql -uroot -p 将数据生成sql文件。
半同步复制实战
主库
1.查看是否支持动态的加载:select @@have_dynamic_loading;
2.show plugins;查看可支持的插件
3.install plugin rpl_semi_sync_master soname 'semisync_master.so';安装插件
4.show variables like '%semi%';查看半同步复制
5.设置开启半同步复制功能:
set global rpl_semi_sync_master_enabled=1;set global set global rpl_semi_sync_master_timeout=1000;
从库
1.install plugin rpl_semi_sync_slave soname 'semisync_slave.so'; 安装插件
2.set global rpl_semi_sync_slave_enabled=1;
3.stop slave; start slave;
在var文件夹下,查看mysql.log,就能看到我们的执行日志。
并行复制实战
主库
1.show variables like '%binlog_group%';
2.set global binlog_group_commit_sync_delay=1000;每组同步提交延迟设置
3.set global binlog_group_commit_sync_no_delay_count=100;一个组里面有多少事务数
从库
1.show variables like '%slave%'
2.set global slave_parallel_type='LOGICAL_CLOCK';// 设置
3.set global slave_parallel_workers =8;// 设置线程数
4.show variables like '%relay_log%';
5.set global relay_log_recovery=1; 【可读模式下需要进入my.cnf中添加属性】
set global relay_log_info_repository='table';
?
主从同步延迟问题解决方案:
读写分离实战 - 借助中间件
1.新增一台主机134:proxy (记着关闭防火墙等操作)
2.下载mysql-proxy-0.8.8.5-linux-el6-x86-64bit.tar.gz
3.tar -xzvf mysql-proxy-0.8.8.5-linux-el6-x86-64bit.tar.gz
4.vim /etc/mysql-proxy.cnf :
[mysql-proxy]
user=root
admin-username=root //用户名
admin-password=root //密码
proxy-address=192.168.95.134:4040 // 代理服务的地址
proxy-backend-addresses=192.168.59.130:3306 //设置写库的地址
proxy-read-only-bankend-addresses=192.168.95.132:3306,ip:port,ip:port // 设置读库的地址
proxy-lua-script=/root/mysql-proxy-0.8.8.5-linux-el6-x86-64bit/share/doc/mysql-proxy/rw-splitting.lua //指定lua脚本运行位置
log-file=/var/log/mysql-proxy.log //日志位置
log-level=debug //日志级别
daemon=true //进程方式,后台进行
keepalive=true //是否尝试重启
5.chmod 660 /etc/mysql-proxy.cnf //指定文件权限:可读可写
6.修改lua脚本的最小连接数为1,方便后续测试看到效果:vim mysql-proxy-0.8.8.5-linux-el6-x86-64bit/share/doc/mysql-proxy/rw-splitting.lua
7.cd mysql-proxy-0.8.8.5-linux-el6-x86-64bit/bin 进入到bin路径下,开始启动 ./mysql-proxy --defaluts-file=/etc/mysql-proxy.cnf
概念:双主模式是指两台服务器互为主从,其中任意一台的数据发生变更,都会同步到另外一台服务器的数组库。
使用双主双写还是双主单写:?建议使用双主单写,原因是双主双写存在以下问题:
双主模式实战:
1.在之前配置的master主机上进行修改,vim /etc/my.cnf
relay_log=mysql-relay-bin
log_slave_updates=1
#如果是双主双写,为了避免id冲突,可以设置下面两个参数,id从1开始,以2递增
auto_increment_offset=1
auto_increment_increment=2
2.重新启动mysql,systemctl restart mysqld
3.进入mysql命令端,show master status
4.开始配置另外一台新的master,重复之前的操作。show master status;
5.开始指定master1和master2互相复制。以下操作master1和master2都要指定,互相进行指定。
6.change master to master_host='192.168.95.133',master_port=3306,master_user='root',master_password='root',master_log_file='mysql-bin.000001',master_log_pos=884;
然后启动slave:start slave;
测试:create table test1(id int primary key auto_increment,name varchar(20))engine=innodb charset=utf8;
mysql会在内存中保存一些数据,通过LRU算法将一些不常用的数据写入到磁盘中。要尽可能的扩大内存的数据存储,这样的话会提高查询效率。
默认mysql的使用内存存储数据大小为125M,要根据场景需要,如果某台服务器只是用来部署mysql的,可以将mysql的使用内存大小调整到总内存的3/4或者4/5甚至更多,如果这台服务器还部署了其他的应用程序,那就不宜调整那么高,根据实际情况尽可能的多调整。
调整步骤:
show global status like‘innodb_buffer_pool_pages_%’
可以查看innodb_buffer_pool的一些参数当前状态,假如看到了innodb_buffer_pool_pages_free = 0
表示内存已经用光。innodb_buffer_pool_size = 750M
表示将mysql内存使用空间扩大到了750M。默认情况下,只有当数据被读取过一次后,才能将数据缓存在innodb_buffer_pool中。
因此我们可以将数据进行预热,在mysql启动时就将硬盘中所有的数据加载到内存中,数据预热能够提高查询的效率。
(1)数据预热的脚本
SELECT DISTINCT
?CONCAT('SELECT ',ndxcollist,' FROM ',db,'.',tb,
??' ORDER BY ',ndxcollist,';') SelectQueryToLoadCache
??FROM
?(
????SELECT
?????engine,table_schema db,table_name tb,
?????index_name,GROUP_CONCAT(column_name ORDER BY seq_in_index)
ndxcollist
????FROM
???(
??????SELECT
???????B.engine,A.table_schema,A.table_name,
???????A.index_name,A.column_name,A.seq_in_index
??????FROM
???????information_schema.statistics A INNER JOIN
???????(
??????????SELECT engine,table_schema,table_name
??????????FROM information_schema.tables WHERE
?????????engine='InnoDB'
???????) B USING (table_schema,table_name)
??????WHERE B.table_schema NOT IN ('information_schema','mysql')
??????ORDER BY table_schema,table_name,index_name,seq_in_index
???) A
????GROUP BY table_schema,table_name,index_name
?) AA
ORDER BY db,tb;
将该脚本保存为loadtomem.sql
(2)执行命令
mysql -uroot -proot -AN < /root/loadtomem.sql > /root/loadtomem.sql
(3) 在需要数据预热时,重启数据库
mysql -uroot < /root/loadtomem.sql > /dev/null 2>&1
(1) 增大redo_log,减少落盘次数
将innodb_log_file_size设置成0.25 * innodb_buffer_pool_size
(2) 生产中不开启慢查询日志,只有遇到问题时才开启慢查询日志排查问题。
(3) 写redo_log策略innodb_flush_log_at_trx_commit设置为0和2
如果不涉及安全性比较高的金融系统操作,或者事务要求都特别小,是可以将redo_log的策略设置为0或者2,以减少I/O次数
使用SSD或者内存硬盘
一般针对于统计类型的功能,实时性不高的可以设计中间表。
为了减少表之间的关联,应适当的增加一些冗余的字段,可以有效的提高查询效率。
对于单表中的字段太多(比如一个表中有100条数据)可以进行拆表,如果不经常使用的一些属性以及存储数据较多的一些字段,可以单独拆出一个表来。
每张表建议都要有一个主键(主键索引),建议设置为int类型,自增原则(不考虑分布式场景下时可如此,分布式下建议使用雪花算法。)
数据库的表越小,那么在它上面执行查询效率也就越高。
因此再给数据库表字段设置宽度时尽可能的设计的小。
另外,尽量把字段设置为NOT NULL,这样表中不存在NULL值,在查询的时候就不需要单独再去比较NULL值了,mysql中的NULL值查询很耗费资源。
对于一些省份等字段,可以考虑将字段设置为ENUM属性,因为在mysql数据库中,ENUM属性的查询和数值效率差不多高,要高于varchar类型,下面将写一段关于ENUM类型使用的代码。
# 首先我本地数据库中存在这么一个表格persion3,它的原本字段有(id,name,sex,address,time)
# 首先创建该类型的值时要初始化几个值,比如要增加province字段时,默认要在山东,河南,江苏中选择城市
alter table persion3 add column province enum('山东','河南','江苏') collate utf8_bin default NULL comment '省份' after time;
# 然后新增一条数据时,需要在enum定义的组里面写
insert into persion3 values('1','zae',1,'北京',now(),'山东')
# 下面演示一种错误的写法,因为山西没有在ENUM中定义
insert into persion3 values('1','zae',1,'北京',now(),'山西')
另外,设置字段时能用数值的就用数值,例如sex可以使用0或者1