1.数据库基本原理
1.1事务的四大特性
原子性:事务是最小的执行单位,要么全部执行,要么全部不执行
一致性:执行事务前后,数据保持一致,多个事务读取的结果是相同的
隔离性:并发访问数据时,一个用户事务不会被其他事务所干扰
持久性:事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。
![](https://img-blog.csdnimg.cn/282b740dfe2548128da4a290cc9002f8.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAbHlkb244ODg=,size_20,color_FFFFFF,t_70,g_se,x_16)
1.2 事务的隔离级别
事务的隔离级别共有四种,包括读未提交、读以提交、可重复读、可序列化,由低到高依次为Read uncommitted、Read committed、Repeatable read、Serializable,这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。
-
Read uncommitted 读未提交:事务B读取了事务A尚未提交的数据,可能出现脏读
-
Read committed 读提交:事务A事先读取了数据,事务B紧接了更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变,避免了脏读,但是可能会造成不可重复读
-
Repeatable read 重复读: ,避免了不可重复读,但还有可能出现幻读。注:MySQL的默认隔离级别就是Repeatable read。
-
Serializable 序列化:Serializable是最高的事务隔离级别,同时代价也花费最高,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻像读。
重复读与幻读
重复读是为了保证在一个事务中,相同查询条件下读取的数据值不发生改变,但是不能保证下次同样条件查询,结果记录数不会增加。
幻读就是为了解决这个问题而存在的,他将这个查询范围都加锁了,所以就不能再往这个范围内插入数据,这就是SERIALIZABLE 隔离级别做的事情。
隔离级别与锁的关系
- 在Read Uncommitted级别下,读操作不加S锁;
- 在Read Committed级别下,读操作需要加S锁,但是在语句执行完以后释放S锁;
- 在Repeatable Read级别下,读操作需要加S锁,但是在事务提交之前并不释放S锁,也就是必须等待事务执行完毕以后才释放S锁。
- 在Serialize级别下,会在Repeatable Read级别的基础上,添加一个范围锁。保证一个事务内的两次查询结果完全一样,而不会出现第一次查询结果是第二次查询结果的子集。
1.3 数据库的引擎
1.3.1 数据库引擎简介
数据库引擎:用于存储、处理、保护数据的核心服务。
当你访问数据库时,不管是手工访问,还是程序访问,都不是直接读写数据库文件,而是通过数据库引擎去访问数据库文件,常见的数据库引擎有如下几种:Innodb 、 MyISAM 、MEMORY 、MERGE,我们可以通过指令 查看数据库下的引擎
常见的数据库引擎有如下两种:分别是innoDB(聚簇式)和MYISAM(非聚簇式)
1.3.2 InnoDB与MyISAM区别,优缺点,使用场景
1).MyISAM是非事务安全型的,而InnoDB是事务安全型的。
2).MyISAM锁的粒度是表级,而InnoDB支持行级锁定。
3).MyISAM支持全文类型索引,而InnoDB不支持全文索引。
1.4 数据库索引
索引就类似于文章的目录,是用于方便的查找和获取数据库中的数据所设置的数据查询工具,他编排了数据库的数据的存储方式,也我们快速获取数据的一个重要指引。常见的两种索引结构包括B-tree和Hash.
B-tree
B-tree索引能够加快访问数据的速度,因为存储引擎不再需要经行全表扫描来获取需要的数据,取而代之的是从根节点开始搜索。根节点的槽中存放了指向子节点的指针,存储引擎根据这些指针向下查找。通常比较节点页的值和要查找的值可以找到合适的指针进入下层子节点。
B-tree通常意味着所有的值都是按顺序存储的,并且每一个叶子页到根的距离相同。
哈希索引(hash)
Mysql中只有在memory引擎显示支持哈希索引。
哈希索引基于哈希表实现,只有精确匹配索引所有列的列才有效。对于每一行数据,存储引擎都会对所有索引计算一个哈希码,哈希码是一个较小的值并且不同键值计算出来的哈希码都不一样。哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据的指针。
Mysql常见的索引:主键索引、唯一索引、普通索引、全文索引、组合索引
唯一索引
与普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值(注意和主键不同)。如果是组合索引,则列值的组合必须唯一。
主键索引
它是一种特殊的唯一索引,不允许有空值。
组合索引
平时用的SQL查询语句一般都有比较多的限制条件,所以为了进一步榨取MySQL的效率,就要考虑建立组合索引。
全文索引
在前面描述中,在B-tree中可以通过列前缀进行查询。
2.sql优化进阶操作
2.1 分页分表
当需要从数据库查询的表有上万条记录的时候,一次性查询所有结果会变得很慢,特别是随着数据量的增加特别明显,这时需要使用分页查询。对于数据库分页查询,也有很多种方法和优化的点。
使用子查询优化
这种方式先定位偏移位置的 id,然后往后查询,这种方式适用于 id 递增的情况。
select * from orders_history where type=8 and
id>=(select id from orders_history where type=8 limit 100000,1)
limit 100;
使用 id 限定优化
这种方式假设数据表的id是连续递增的,则我们根据查询的页数和查询的记录数可以算出查询的id的范围,可以使用 id between and 来查询:
select * from orders_history where type=2
and id between 1000000 and 1000100 limit 100;
使用临时表优化
这种方式已经不属于查询优化,这儿附带提一下。
对于使用 id 限定优化中的问题,需要 id 是连续递增的,但是在一些场景下,比如使用历史表的时候,或者出现过数据缺失问题时,可以考虑使用临时存储的表来记录分页的id,使用分页的id来进行 in 查询。这样能够极大的提高传统的分页查询速度,尤其是数据量上千万的时候。
2.2 分库分表
数据库分布式核心内容无非就是数据切分(Sharding),以及切分后对数据的定位、整合。数据切分就是将数据分散存储到多个数据库中,使得单一数据库中的数据量变小,通过扩充主机的数量缓解单一数据库的性能问题,从而达到提升数据库操作性能的目的。
数据切分根据其切分类型,可以分为两种方式:垂直(纵向)切分和水平(横向)切分
1、垂直(纵向)切分
垂直切分常见有垂直分库和垂直分表两种。
垂直分库就是根据业务耦合性,将关联度低的不同表存储在不同的数据库。做法与大系统拆分为多个小系统类似,按业务分类进行独立划分。与"微服务治理"的做法相似,每个微服务使用单独的一个数据库。如图:
垂直分表是基于数据库中的"列"进行,某个表字段较多,可以新建一张扩展表,将不经常用或字段长度较大的字段拆分出去到扩展表中。在字段很多的情况下(例如一个大表有100多个字段),通过"大表拆小表",更便于开发与维护,也能避免跨页问题。
垂直切分的优点:
- 解决业务系统层面的耦合,业务清晰
- 与微服务的治理类似,也能对不同业务的数据进行分级管理、维护、监控、扩展等
- 高并发场景下,垂直切分一定程度的提升IO、数据库连接数、单机硬件资源的瓶颈
缺点:
- 部分表无法join,只能通过接口聚合方式解决,提升了开发的复杂度
- 分布式事务处理复杂
- 依然存在单表数据量过大的问题(需要水平切分)
2、水平(横向)切分
当一个应用难以再细粒度的垂直切分,或切分后数据量行数巨大,存在单库读写、存储性能瓶颈,这时候就需要进行水平切分了。
水平切分分为库内分表和分库分表,是根据表内数据内在的逻辑关系,将同一个表按不同的条件分散到多个数据库或多个表中,每个表中只包含一部分数据,从而使得单个表的数据量变小,达到分布式的效果。如图所示:
水平切分的优点:
- 不存在单库数据量过大、高并发的性能瓶颈,提升系统稳定性和负载能力
- 应用端改造较小,不需要拆分业务模块
缺点:
- 跨分片的事务一致性难以保证
- 跨库的join关联查询性能较差
- 数据多次扩展难度和维护量极大
1、根据数值范围
按照时间区间或ID区间来切分。例如:按日期将不同月甚至是日的数据分散到不同的库中;将userId为1~9999的记录分到第一个库,10000~20000的分到第二个库。
优点:
- 单表大小可控
- 天然便于水平扩展,后期如果想对整个分片集群扩容时,只需要添加节点即可,无需对其他分片的数据进行迁移
- 使用分片字段进行范围查找时,连续分片可快速定位分片进行快速查询,有效避免跨分片查询的问题。
缺点:
- 热点数据成为性能瓶颈。连续分片可能存在数据热点,例如按时间字段分片,有些分片存储最近时间段内的数据,可能会被频繁的读写,而有些分片存储的历史数据,则很少被查询
2、根据数值取模
一般采用hash取模mod的切分方式,例如:将 Customer 表根据 cusno 字段切分到4个库中,余数为0的放到第一个库,余数为1的放到第二个库,以此类推。这样同一个用户的数据会分散到同一个库中,如果查询条件带有cusno字段,则可明确定位到相应库去查询。
优点:
- 数据分片相对比较均匀,不容易出现热点和并发访问的瓶颈
缺点:
- 后期分片集群扩容时,需要迁移旧的数据(使用一致性hash算法能较好的避免这个问题)
- 容易面临跨分片查询的复杂问题。比如上例中,如果频繁用到的查询条件中不带cusno时,将会导致无法定位数据库,从而需要同时向4个库发起查询,再在内存中合并数据,取最小集返回给应用,分库反而成为拖累。
2.3 mysql性能优化之索引优化
添加三列的复合索引
1 |
|
在BTREE索引的使用上,以下几种情况可以用到该索引或索引的一部分(使用explain简单查看使用情况):
1. 全值匹配
如select * from staffs where name = 'July' and age = '23' and pos = 'dev' ,key字段显示使用了idx_nap索引
2. 匹配最左列,对于复合索引来说,不总是匹配所有字段列,但是可以匹配索引中靠左的列
如select * from staffs where name = 'July' and age = '23',key字段显示用到了索引,注意,key_len字段(表示本次语句使用的索引长度)数值比上一条小了,意思是它并未使用全部索引列(通常这个长度可估摸着用了哪些索引列,埋个坑),事实上只用到了name和age列
再试试select * from staffs where name = 'July',它也用了索引,key_len值更小,实际只用到了索引中的name列
3. 匹配列前缀,即一个索引中列的前一部分,主要用在模糊匹配,如select * fromstaffs where name like 'J%',explain信息的key字段表示使用了索引,但是mysql的B树索引不能非列前缀的模糊匹配,如select * from staffs where name like '%y' 或者 like '%u%',据说是由于底层存储引擎的API限制
4. 匹配范围,如select * from staffs where name > 'Mary',但俺在测试时发现>可以,>=却不行,至少在字符串列上不行(测试mysql版本5.5.12),然而在时间类型(timestamp)上却可以,不测试下还真不能确定说就用到了索引==
2.4 选择合适的分布式主键方案
场景: 分库分表,使用分布式主键方案
比如: 分库分表,通常情况下是因为一个表存储数据数据,分表,把同一张表拆分不同的数据库