MySQL概览
MySQL存储引擎
MyISAM、InnoDB、Memory
MyISAM
不支持事务,不支持外键约束,索引文件和数据文件分开。内存中可以缓存更多的索引,对查询性能会更好,适用于那种少插入,多查询的场景。
经典案例:大数据场景下的报表系统。网站埋点日志、业务系统通过MySQL binlog同步业务数据,批量写入Hadoop后,在 T+1 进行离线批处理计算,
数据计算完,导入MySQL中,后边报表系统就可以直接从MySQL中查询已经计算好的数据,渲染成各种图,供他人使用。
但是在数据量非常多的情况下,几千万、几十亿数据时,MySQL是扛不住这么多数据的,一般建议单表500万以内数据量。
所以后来采用可配置化BI系统 + kylin + elastic search,支持大规模数据的复杂报表。
InnoDB
支持事务,强制要求主键,默认根据主键建立聚簇索引,支持外键约束。高并发(读写分离)、大数据量(分库分表)、高可用(主备切换)等相关成熟的数据库架构。
MySQL索引相关
索引数据结构
B树,B+树
B树
- 平衡的多路查找树,所有叶子结点都位于同一层。
- 一个5阶B树,每个结点最多有4个关键字。
- 每个结点都能存放数据或数据地址。
B+树
- B树的升级版,只有叶子结点才保存数据,所以每次查找次数都相同,更稳定。
- 另外,所有的叶子节点数据构成了一个有序链表,范围查找时性能更高。
- 全节点遍历时,只需要遍历所有的叶子节点即可,遍历速度更快。
MyISAM引擎中的索引实现(B+树)
MyISAM数据文件和索引文件是分开的,所以索引文件里的data都是指向数据文件中的一行。
InnoDB引擎中的索引实现(B+树)
聚簇索引
强制要求主键,默认根据主键建立一个聚簇索引。数据文件本身就存在于根据主键建立的聚簇索引中。
如果没有主键,MySQL会自动找一个唯一列、或者隐式定义一个主键DB_ROW_ID,以此来创建聚簇索引。
非聚簇索引
- 如果查询的列,索引中完全包含,也就是覆盖索引,直接从索引中返回数据即可。
- 如果查询的列,索引中不完全包含,那就会回表查询,根据索引中指向的地址,去聚簇索引中获取数据。
所以,在 InnoDB引擎里边,按照主键ID查询数据是很快的。
InnoDB引擎两个建议
- 不推荐使用 UUID 作为主键
- 推荐使用 自增ID 作为主键
原因如下:
- UUID 作为主键,键值32位字符串,多消耗磁盘空间,会使索引文件变大。
- 自增ID 是有序的,数据插入时,磁盘的顺序读写性能更高,也天然适合范围查找。
- UUID 生成的字符串是无序的,插入时,频繁移动磁盘块,自然性能就会低一些。
索引使用规则
- 全列匹配(联合索引,作为条件时要是有序的)
- 最左原则(如果没法全列匹配,要遵守最左原则,尽可能使最左的索引有较好区分度)
- 前缀匹配(like "xxx%",%不能放前边"%xxx%")
- 范围列匹配(符合最左原则才可以范围匹配,同时范围之后的无法使用索引)
- 函数不能使用索引
索引缺点,注意事项
- 插入性能会降低
- 尽量少创建索引,2~3个
- 索引区分度要高(id【推荐】、status(0, 1)【区分度太低】)
事务
事务的ACID
- 原子性(Atomic)
要么一起成功,要么一起失败
- 一致性(Consistency)
SQL执行前后,数据是一致的
- 隔离性(Isolation)
多个事务之间不能相互干扰
- 持久性(Durability)
事务成功,修改的结果是持久的
事务隔离级别
- 读未提交
可以读到其他事务没有提交的数据,如果别的事务后边没提交,这就不对了
- 读已提交(RC)【Oracle默认】
可以读到已提交的数据。
- 可重复读(RR)【MySQL默认】
通过MVCC来保证,在一个事务里边,多次读同一行数据,无论别的事务怎么修改,本事务读到的内容是一样的。
- 串行化
用来解决幻读的,不过这样会导致性能低下。
MySQL InnoDB如何实现可重复读(RR)
在 InnoDB 引擎下,会为每一行保存两个事务ID:(1)创建的事务ID;(2)删除的事务ID。
删除
id | name | 创建事务ID | 删除事务ID |
---|---|---|---|
1 | 张三 | 120 | 空 |
2 | 李四 | 121 | 空 |
- 事务120进来,插入了一条数据id=1
- 事务121进来,插入了一条数据id=2
- 事务122进来,删除了id=1的数据
之后的数据长这样:
id | name | 创建事务ID | 删除事务ID |
---|---|---|---|
1 | 张三 | 120 | 122 |
2 | 李四 | 121 | 空 |
那么,事务120还能查到id=1的数据吗?
答:可以的,事务120读取的时候,可以读到 创建事务ID <= 当前事务ID,以及当前事务ID <= 删除事务ID。
修改
id | name | 创建事务ID | 删除事务ID |
---|---|---|---|
1 | 张三 | 120 | 空 |
2 | 李四 | 121 | 空 |
- 事务120进来,插入了一条数据id=1
- 事务121进来,插入了一条数据id=2
- 事务122进来,修改id=1的数据,name=小明
之后的数据长这样:
id | name | 创建事务ID | 删除事务ID |
---|---|---|---|
1 | 张三 | 120 | 空 |
2 | 李四 | 121 | 空 |
1 | 小明 | 122 | 空 |
那么,事务120查询id=1的数据的name是啥呢?
答:张三。事务120读取的时候,只能读到 创建事务ID <= 当前事务ID 的数据;InnoDB里边,
如果有不同的事务修改同一行数据,此时每个事务都会创建一个数据行的快照再进行修改。
MySQL锁
(1)表锁、行锁
表锁
在MyISAM引擎下,一般MyISAM会加表锁:
- 执行查询时,会默认加个表共享锁,也就是表读锁,这个时候别人只能来查,不能写数据;
- 写操作的时候,会给表加独占锁,也就是表写锁,别人不能读也不能写。
在InnoDB引擎下:
- 意向共享锁,就是加共享行锁的时候,必须先加这个共享表锁;
- 意向排它锁,就是给某行加排它锁的时候,必须先给表加排它锁。
注意:这个表锁,是InnoDB引擎自动加的,无需手动加锁。
行锁
InnoDB的行锁有共享锁(S)和排它锁(X)两种。
- 共享锁:多个事务可以加共享锁读同一行数据,但是别的事务不能写这行数据;
- 排它锁:就是一个事务可以写这行数据,其他事务只能读,不能写。
自动加锁
- insert/update/delete操作会加行级排它锁。
- select,InnoDB不会加锁。因为InnoDB默认实现了可重复读,也就是MVCC机制,多个事务随便读,各自有快照。
手动加锁
- 手动加共享锁:
select * from table where id = 1 lock in share mode
,然后其他事务就不能来修改这行数据了。 - 手动加排它锁:
select * from table where id = 1 for update
,然后其他事务修改会被hang住,慎用!
- 手动加共享锁:
(2)悲观锁和乐观锁
悲观锁:
只能我自己来操作,别人都操作不了。
select * fromo table where id = 1 for update
乐观锁:
通过版本控制,如果别人修改了,我就不修改了,或者重新获取新值,再进行修改。有点CAS的意思。
select id, username, version
from table
where id = 1;
update table
set username = 'New Name',
version = #{old_version} + 1
where id = 1
and version = #{old_version};
(3)死锁
多个事务竞争同一把锁,就会形成死锁。
实战:
死锁相对常见,如下
- 有些时候,调整事务隔离级别可以解决。
- 查看事务隔离级别
SELECT @@tx_isolation;
- 修改数据库默认事务隔离级别;代码中配置事务隔离级别;多租户下,连接池设置事务隔离级别
- 查看事务隔离级别
- 看下哪里死锁了
Lock wait timeout exceeded; try restarting transaction
- 查看死锁SQL
select * from information_schema.innodb_trx;
MySQL调优手段
- 尽可能单表查询,加上索引,join相关的逻辑放在代码里边
- explain对应SQL,看是否命中了索引,索引扫描了多少行
主键数据类型选型
推荐使用自增ID,那么如果自增ID用完了怎么办?
int
- 带符号 -21,4748,3648 ~ 21,4748,3647
- 无符号 0 ~ 42,9496,7295(约42亿)
bigint
- 带符号 -922,3372,0368,5477,5808 ~ 922,3372,0368,5477,5807
- 无符号 0 ~ 1844,6744,0737,0955,1615
- 默认是int类型,自增ID最大值 4294967295,可以改为bigint类型
- 怎么修改,直接Alter修改肯定需要停服,大概率是可以停服的
- 如果不可以停服,主从切换,分别修改
- 另外,单表数据量过亿,系统肯定早就应该重构了
- 分库分表概括一下
- 分库分表主键ID可采用雪花算法,或者其他分布式ID解决方案
幂等性
INSERT INTO ... ON DUPLICATE KEY UPDATE
语法,不存在时插⼊,存在时更新,是天然⽀持幂等性的。
REPLACE INTO ...
如果唯一键存在就更新。
INSERT IGNORE INTO ...
如果唯一键存在就不插入,也可以解决并发插入的问题。