MYSQL 基础概念
1.基础架构:SQL查询语句到执行
MySQL可以分为Server层和存储引擎层
- Server 层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能。
- 存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。 MySQL 5.5.5 版本开始,InnoDB成为了默认存储引擎。
- create table 默认使用InnoDB,否则要指定存储引擎。
- 不同存储引擎共用一个Server层
1.1.连接器
- 负责跟客户端建立连接,获取权限,维持和管理连接
- mysql -h$ip -P$port -u$user -p
- 密码不正确 > Access denied for user 客户端程序结束
- 密码认证通过 > 连接器查出你拥有权限。
- 空闲状态 show processlist > Sleep
- 自动断开 默认8小时 wait_timeout参数控制
- 长连接:连接成功后,如果客户端持续有请求,则一直使用同一连接。
- 因为连接过程较复杂,尽量使用长连接。
- 长连接过多,有时造成内存占用太大,最终连接会被系统强行杀掉。
- 短连接:每次执行完成很少几次查询后就断开。
方案:1.定期断开长连接;2.执行一个较大操作后,执行mysql_reset_connection重新初始化连接资源(过程不需要重连和权限认证)
1.2.查询缓存
- 接到查询请求后,先查询缓存。
- 之前执行的语句和结果以key-value对形式缓存在内存中
- 否则继续执行阶段
- 查询缓存弊大于利
- 静态表,比如系统配置表适合查询缓存
- 参数query_cache_type为DEMAND ,默认不使用查询缓存
- 显式指定 mysql> select SQL_CACHE * FROM T where ID=10;
- MYSQL 8.0彻底没有缓存
1.3.分析器
- 词法分析
- 语法分析
1.4.优化器
- 表里有多个索引时,决定使用哪个索引
- 一个语句有多表关联join,决定各个表连接顺序。
1.5.执行器
- 判断是否执行权限
- 打开表,根据表的引擎定义,使用引擎接口
- 慢查询日志 rows_examined 字段:执行过程扫描了多少行。
2.日志系统:更新语句的执行
回顾:查询语句执行过程:经过连接器、分析器、优化器、执行器等功能模块,最后到达存储引擎。
mysql> create table T(ID int primary key, c int);
mysql> updata T set c=c+1 where ID=2;
2.1.redolog
- WAL: Write-Ahead Logging 先写日志,再写磁盘
- 一条记录更新,InnoDB引擎先将记录写入redo log里,并更新内存;适当时候将这个操作记录更新到磁盘里。
- 固定大小,比如一组4个文件,每个文件1G
- crash-safe 保证数据库发生异常重启,之前提交的记录不丢失。
- innodb_flush_log_at_trx_commit设置1,每次事务都持久化到磁盘
2.2.binlog 归档日志
- Server层:功能层 》binlog
- 引擎层:负责存储相关事宜 》 redolog
- sync_binlog 参数设置1,每次事务的binlog都持久化到磁盘
2.3.两种日志区别
- redolog是InnoDB 特有;binlog是server层实现,所有引擎都可以使用
- redolog是物理日志,记录某个数据页做的修改;binlog是逻辑日志
- redolog是循环写;binlog追加写入
2.4.两阶段提交
- 两阶段提交是跨系统维持数据逻辑一致性时常用的一个方案。
- redo log 和binlog都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。
2.5.双1安全参数
- redo log 用于保证 crash-safe 能力。innodb_flush_log_at_trx_commit 这个参数设置成 1 的时候,表示每次事务的 redo log 都直接持久化到磁盘。这个参数我建议你设置成 1,这样可以保证 MySQL 异常重启之后数据不丢失
- sync_binlog 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘。这个参数我也建议你设置成 1,这样可以保证 MySQL 异常重启之后 binlog 不丢失
3.事务隔离
3.1.隔离性和隔离级别
ACID(Atomicity,Consistency,Isolation,Durability原子性,一致性,隔离性,持久性)
- 脏读 dirty read
- 不可重复读 non-repeatable read
- 幻读 phantom read
隔离与效率
- 隔离越严实,效率越低
- 需要在隔离和效率间寻找平衡点
SQL标准事务隔离级别
- 读未提交 read uncommitted 一个事务还没有提交,它做的变更就能被别的事务看到。
- 读提交 read committed 一个事务提交之后,它所做的变更才会被其他事务看到。
- 可重复读 repeatable read 一个事务执行过程中看到的数据,总是跟这个事务启动时看到数据是一致的。未提交变更对其他事务也是不可见的。
- 串行化 serializable 对于同一行记录,写会加锁,读会加读锁。出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
create table T(c int) engine=InnoDB insert into T(c) values(1);
事务A | 事务B |
---|---|
启动事务查询得到值1 | 启动事务 |
查询得到值1 | |
将1改成2 | |
查询得到值V1 | |
提交事务B | |
查询得到值V2 | |
提交事务A | |
查询得到值V3 |
模式 | V1 | V2 | V3 | 视图 |
---|---|---|---|---|
读未提交 | 2 | 2 | 2 | 直接返回记录上的值 |
读提交 | 1 | 2 | 2 | 每个SQL语句执行时创建视图 |
可重复读 | 1 | 1 | 2 | 事务启动时创建视图 |
串行化 | 1 | 1 | 2 | 直接用加锁方式并并行访问 |
ORACLE默认隔离级别是读提交,迁移ORACLE至MySQL将隔离级别设置为读提交
- show variables like ‘transaction_isolation’
- READ-COMMITTED
3.2.事务隔离的实现:回滚日志
- MVCC 多版本并发控制
- 当系统里没有比这个回滚日志更早的read-view时,删除回滚日志。
- MySQL 5.5 以前版本,回滚日志和数据字典一起放在ibdata文件,及时长事务提交,回滚段被清理,文件也不会变小,最终导致重建整个库
3.3.事务的启动方式
- 显式启动事务,begin或start transaction 提交commit 回滚 rollback
- set autocommit=0 自动提交关闭,事务直到commit或rollback
- 客户端连接架构默认set autocommit=0 导致长事务。
- 建议 set autocommit=1 通过显式语句方式启动事务。
3.4.commit work and chain语法
- 提交事务并自动启动下一个事务
select * from information_schema,innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60
查询持续时间超过60s的事务
3.5.避免长事务对业务影响
应用开发端
- 确认是否使用了set autocommit=0。检查general_log ,跑一个业务逻辑。目标改成1
- 确认是否有不必要的只读事务。确认语句有没有必要使用begin/commit。
- 业务连接数据库,通过SET MAX_EXECUTION_TIME,控制每个语句执行最长时间 数据库端
- 监控information_schema.Innodb_trx表,设置长事务阀值,超过就报警或着kill;
- Percona的pt-kill工具
- 在业务功能测试阶段要求输出所有general_log,分析日志行为提前发现问题;
- 如果使用mysql5.6以上版本,innodb_undo_tablespaces设置2或更大,如果出现大事务导致回滚段过大,这样设置后清理起来更方便。
4.索引
4.1.索引常见模型
- 哈希表
- 键-值 key-value存储数据结构
- 适用于只有等值查询场景,比如Memcached以及一些NoSQL引擎
- 有序数组
- 等值查询和范围查询场景中的性能都优秀
- 查询效率高,更新数据效率低
- 只适用于静态存储引擎,比如某市人口信息
- 搜索树
- 二叉树时间复杂度O(log(N))
- N叉树 ,性能优点适配磁盘访问模式,广泛应用在数据库引擎。
4.2.InnoDB索引模型
- 表都根据主机顺序以索引的形式存放–索引组织表
- B+树索引模型
create table T( id int primary key, k int not null, name varchar(16), index (k)engine=InnoDB; )
- 主键索引(聚簇索引clustered index)和非主键索引(二级索引secondary index)
- 主键索引叶子节点存整行数据
- 非主键索引叶子节点内容是主键的值
4.3.基于主机索引和普通索引的查询区别
- 主键查询:搜索ID这棵B+树
- 普通索引查询:先搜索索引树,得到主键,再在主键索引树搜索一次。这个过程称为回表。
4.4.索引维护
- 页分裂
- 页合并
- 自增主键 NOT NULL PRIMARY KEY AUTO_INCREMENT 每次插入新记录,是追加操作,不会触发叶子节点分裂(性能和存储考虑最优)
- 业务逻辑字段做主键,不容易保证有序插入。
- 主键长度越小,普通索引的叶子节点越小,普通索引占用空间越小
4.5.知识扩展
重建索引方法1
alter table T drop index k;
alter table T add index(k);
重建索引方法2
alter table T drop primary key;
alter table T add primary key(id);
create table T(
ID int primary key,
k int NOT NULL DEFAULT 0,
s varchar(16) NOT NULL DEFAULT '',
index k(k)
engine=InnoDB;
)
insert into T values(100,1,'aa'),(200,2,'bb'),(300,3,'cc'),(500,5,'ee'),(600,6,'ff'),(700,7,'gg');
4.6.覆盖索引
- select ID from T where k between 3 and 5
- 减少树的搜索次数,提升查询性能。
4.7.联合索引
- 根据市民身份证查询他的姓名
4.8.最前缀原则
- 第一原则,如果通过调整顺序,可以少维护一个索引,那么这个顺序往往就是需要优先考虑采用的。
- 考虑空间
- 索引下推
6.全局锁和表锁
6.1.全局锁
- 整个数据库实例加锁
- Flush tables with read lock(FTWRL)
- 阻塞:数据更新语句、数据定义语句、更新类事务提交语句。
- 典型应用场景,全库逻辑备份
- mysqlddump 使用参数single-transaction 导数据之前,启动一个事务,确保拿到一致性视图,
- mysqlddump的一致性读是好,但不是所有引擎支持这个隔离级别,所以需要FTWRL
- single-transaction方法只适用于所有表使用事务引擎的库,否则备份只能通过FTWRL,这便是InnoDB替代MyISAM愿意之一。
6.2.readonly
- 有些系统中,readonly值被用来做其他逻辑,比如判断一个库是主还是备。
- 异常处理机制上有差异。
- FTWRL ,客户端发生异常断开,mysql自动释放全局锁
- readonly,客户端发生异常,数据库一直保持readonly状态,风险高。
6.3.表级锁
- 表锁
- lock table…read/write
- 可用unlock tables主动释放锁
- 可断开客户端释放锁
- 即限制其他线程,也限定本线程接下来操作对象
- 元数据锁 meta data lock (MDL) MySQL5.5以上
- 不需要显式使用,访问表时自动加上
- 保证读写正确性
6.4.小表加字段
- 解决长事务,避免一直MDL锁
- information_schema 库里的innodb_trx表中,查询当前执行中的事务,考虑先暂停DDL或着kill长事务。
- alter table设定等待时间。
- ALTER TABLE tbl_name NOWAIT add column
- ALTER TABLE tbl_name WAIT N add column
6.5.两阶段锁
- 在InnoDB事务中,行锁是在需要时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。
- 如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。
6.6.死锁
- 策略1,直接进入等待,直到超时。超时时间可以通过参数innodb_lock_wait_timeout来设置。
- 策略2,发起死锁检测,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。innodb_deadlock_detect设置on,表示开启这个逻辑。
- innodb_lock_wait_timeout默认50s
- 并发业务死锁检测要消耗大量CPU资源
- 临时把死锁检测关掉,业务有损。
- 控制并发度
- 中间件控制
- 一行改成逻辑上的多行来减少锁冲突。
7.1.事务隔离
默认autocommit=1
begin/start transaction 命令并不是一个事务的起点,在执行它们之后第一个操作InnoDB表的语句,事务才真正启动。
马上启动一个事务: start transaction with consistent snapshot
mysql> CREATE TABLE 't' (
'id' int(11) NOT NULL,
'k' int(11) DEFAULT NULL,
PRIMARY KEY ('id')
) ENGINE=InnoDB;
insert into t(id,k) values(1,1),(2,2)
MySQL两个视图
- 一个是view,一个用查询语句定义的虚拟表,在调用的时候执行查询语句并生成结果。(create view….)
- 一个事InnoDB在实现MVCC时用到的一致性读视图,即consistent read view,用于支持RC和RR隔离级别的实现
- RC Read Committed 读提交
- RR Repeatable Read 可重复读