数据库优化
数据库的优化是多方面的,我们以解决系统的瓶颈、减少资源占用、增加反应速度为核心思想去优化。
针对查询:
- 采用索引(防止索引失效 --like 联合索引 or。利用索引优化大偏移量的查询,如:limit 10000,20 如果采用索引覆盖查询得到主键值,再通过一次关联操作,连接需要查询的字段,可以防止全表扫描,较好地发挥数据库的性能)
- 使用连接代替子查询(因为子查询会产生虚拟表,若子查询产生的虚拟表过大,则会占用较大的资源,降低MySQL的响应速度)的方法提高查询速度。
- 针对经常进行连表查询的数据增加中间表,加快查询速度。
针对慢查询:可以去分析慢查询日志,发现引起慢查询的原因。
针对插入:若不禁用索引,每插入一条数据都需要对索引进行相应的维护,可以在插入时禁用索引、禁用检查(唯一性检查、外键检查)、禁止自动提交提高插入速度,在插入之后再启用索引和检查、重建索引。
针对数据库结构:垂直拆分(当某些列频繁地被查询,应该进行垂直拆分)、水平拆分(当需要进行高并发写操作,或者说表的存储空间出现瓶颈,应该进行水平拆分)、主从复制实现读写分离、增加中间表、增加冗余字段等方式进行优化。
垂直拆分和水平拆分虽然对性能提升有帮助,但是由于其存在跨表事务,在保持数据一致性的问题上难度更大。尤其是水平拆分,需要选择一个合适的Sharding key 在查询的时候,通过Sharding key值找到数据所在的表,而不是扫描所有表,并且其对并发控制以及隔离级别的选择、事务回滚、索引优化查询的难度更大,水平分库后往往需要采用可串行的事务执隔离级别才可以较好的解决数据一致性问题。
如果一个表中有上千万条数据怎么办
针对查询:优化SQL和索引,使用连接代替子查询,SQL语句针对分区表做优化(定位到相应的分区里进行查找,防止全表扫描)、防止索引失效、针对大偏移量的分页查询采用索引覆盖查询+内联查询的方式进行优化。
针对慢查询:分析慢查询日志,进行优化。
针对插入:每一次插入操作若都需要对索引进行维护或者进行检查,将会消耗较大的性能。插入前禁用索引、检查(唯一性检查、外键检查),在插入结束后,再恢复索引和检查,并重建索引。
针对数据库结构:进行水平拆分、垂直拆分、主从复制实现读写分离、搭建集群分布式地部署数据库。
MySQL慢查询优化
MySQL的慢查询日志默认是关闭的,可以通过配置my.ini或者my.cnf中的slow_quer_log选项打开,也可以启动MySQL时候使用 --slow-query-log[=filename]打开。还需要再my.ini或者my.cnf文件中配置long_query_time选项指定记录的阈值。
在启动慢查询日志服务以后,如果查询的时间超过了long_query_time阈值将会将这个查询过程记录到慢查询日志中。
如何分析慢查询日志
首先找到慢查询日志的路径
SHOW VARIABLES LIKE 'slow_query_log_file';
假设我们得到的慢查询日志文件路径为/var/log/mysql/slow.log
然后使用慢查询日志分析工具
mysqldumpslow /var/log/mysql/slow\.log -t 10 -s c
-t 10 表示Top10 显示前十条数据 -s c表示通过COUNT(次数)进行sort(排序)
可以看到这10条数据的耗时,找到耗时长且出现次数多的语句。
利用EXPLAIN关键字模拟优化器执行SQL语句,来分析SQL查询语句。
EXPLAIN返回的结果中需要关注这些:
- key_len越大,索引占用的存储空间将会越大,IO的消耗也会越大,但是也并不是越小越好,因为如果太小,索引的区分性可能会急剧变差。
- Using filesort表示没有使用索引进行排序,性能较差。Using temporary表示在查询的过程中建立了临时表,通常发生在GROUP BY没有索引的字段的时候 和进行子查询的时候,性能较差。
在分析完慢查询日志以后,需要进行优化:
- 索引:防止索引失效(or、like%、联合索引最左前缀),偏移量较大的分页查询中,采用 索引覆盖查询(limit)+一次inner join实现(如limit 10000,20)
- 用关联查询代替子查询,子查询会产生中间表,当产生的中间表较大,性能较差。
- 将关联查询分解成两次单表查询,然后在应用层对查询出的结果进行关联。
说一说对MySQL的引擎的了解
- MySQL存在多个搜索引擎:InnoDB、myIsam、Memory等,可以对一个mySQL服务器的不同表设置不同的引擎。
- 不同搜索引擎支持的索引类型不一样,InnoDB、myIsam都只支持B树索引,而Memory支持hash索引。InnoDB、MyISam都支持数据的持久化而Memory不支持数据的持久化。
- InnoDB支持事务,myIsam不支持事务。InnoDB是一种具有提交、回滚、崩溃恢复能力的事务安全存储引擎,它满足ACID的特性。InnoDB是事务型引擎的首选。
- InnoDB是为处理巨大数据量的最大性能设计:它支持行级锁定和事务,在高并发的场景下也可以保证较高的数据一致性。它的行级锁定依赖每个页上的bitmap,在获得和释放锁资源时,只需要对行数据在bitmap中对应的位置0或置1即可,性能开销极低。在高并发场景下性能极高。
- InnoDB RR解决幻读,而myIsam在RR隔离级别下没有解决幻读问题。(Elasticsearch通过给文档数据加版本号的方法,也解决了幻读)InnoDB存储引擎,在RR的隔离级别下,依赖NEXT Key Lock 和MVCC技术,就已经解决了脏读、不可重复读、幻读等问题。在进行当前读操作时,利用NEXT Key Lock技术,解决幻读问题。在进行快照读时,采用MVCC技术解决幻读问题,MVCC技术是一种乐观锁的机制,数据的隐藏字段中有指向Undolog的指针、每条数据都有这个指向UndoLog的指针,因此可以构成一条基于Undolog的版本链进行版本的回滚,RR隔离级别下,第一次进行快照读时会生成ReadView,利用ReadView和可见性算法决定这个事务中的哪个版本是可见的,整个过程并不会加锁,非常适合高并发场景。
- InnoDB的索引和表的行数据存在一个文件中,而myIsam的索引和表的行数据存在两个文件下。
- InnoDB存储引擎具有很高的扩展性,可以用它搭建集群,因为它支持事务和行级锁定,并且具有负载均衡和自动故障切换因此InnoDB搭建的集群可以保证更好的数据一致性和并发性能。虽然MyISam和Memory也支持集群搭建,但是MyISam不支持事务,高并发场景下容易出现数据不一致的情况。而Memory则不能进行数据持久化操作,它是基于内存的,数据库若宕机,数据则丢失。
- InnoDB支持外键,可以保证引用数据的完整性。
- InnoDB支持自适应哈希索引,根据数据的访问频率,InnoDB会自动创建自适应hash索引,减少IO次数,提高查询应能。
- InnoDB,MyISam的索引是B+树的结构,可以在树深度较低的情况下,存储海量数据,大大降低查询时的IO次数。以InnoDB的B+树为例,其数据是以页为单位进行存储的,一个页的大小为16KB,一个区包含64个页,区的空间是连续分配的。在页数较少时,是以碎片区存在的,碎片区不属于段,直接从属于表空间。暂且不提InnoDB的存储结构,继续谈索引。刚刚说到,在InnoDB中是以页为单位进行存储的,一个页的大小是16kb,假设我们的一条数据大小是16B,也就是说一个页可以存放1024条数据。B+树的数据全部存储在叶子结点中,B+树的非叶子节点存储的都是目录项,以及目录项的目录项。在叶子结点层:数据行在叶子结点中以主键值从小到大的单向链表的形式进行存储,数据页之间也按从小到大的顺序以双向链表的形式进行连接。目录项层也是如此,假设一个目录项的大小也为16B,则一个深度为4的B+树可以存储102410241024条数据,这就已经超过了10亿。在存储了10亿条数据的聚簇索引中,找出我们的目标数据行,由于树的深度为4,我们只需要4次IO。这大大减少了在海量数据中进行查询的IO次数,增加了查询效率。
- MyIsam不支持事务,不支持外键,并且其表的行数据和索引是分开存放的,索引列的值允许为空。
- MyIsam没有聚簇索引,可以没有主键,而InnoDB必须有主键,如果没有主键,则生成一个长度为6字节的隐式主键,并且InnoDB一定存在聚簇索引。
- MyIsam的索引进行查询时,一定会进行回表查询。其叶子结点的Data域存放的不是行数据,而是行数据的实际物理地址(偏移量)。通过这个偏移量,可以直接从数据文件中读取到对应的行数据。而InnoDB的二级索引的叶子结点的Data域存放的是该行数据的主键值,回表时会走聚簇索引。所以在进行二级索引查询时,MyIsam的二级索引比InnoDB的二级索引略快。
说一说redo log、Undo Log、Bin Log
redo log
redo log叫做重做日志,用来保证事务的持久性。InnoDB存储引擎是要将数据存储到磁盘中的,若每次操作都要直接直接对磁盘中的数据进行读写,也就是说每次都要进行IO操作,这将会是非常慢的。InnoDB存储引擎中的Buffer Pool就是用来解决这个问题的,它存在于内存中,每次对数据进行读写时,都会先查看Buffer Pool中是否存在这条数据,如果Buffer Pool中存在该数据,则直接对Buffer Pool中的数据进行操作。如果Buffer Pool中不存在该数据,则将磁盘中的该数据页加载到Buffer Pool中。也就是说,我们的写操作,是要对Buffer Pool进行的,Buffer Pool会将我们的写操作的数据页标记成脏页,然后再由Buffer Pool将脏页中的数据定时刷新到磁盘上。因为Buffer Pool存在于内存中,因此若在刷新到磁盘前,服务器宕机了,数据就丢失了,重新上电以后也无法恢复数据。这就违背了持久化的特性。因此InnoDB采用了日志优先写的策略,在进行数据写入时,先将写入的数据顺序写入到redolog中,然后再将其写入Buffer Pool,定时对Buffer Pool中的脏页进行刷盘。因为写入redolog的过程属于顺序IO,这个过程是十分迅速的。而脏页可能位于磁盘的不同位置,因此数据顺序写入Undolog,速度快于直接对BufferPool进行刷盘操作。如果服务器宕机了,redolog中的记录也不会丢失,可以进行崩溃恢复。
redolog中有两个结构:redolog BUffer(重做日志缓冲),redolog File(重做日志文件)。在进行COMMIT时,Redolog BUffer中的数据是易丢失的,必须要将Redolog BUffer中的数据持久化到Redolog File中的时候才算COMMIT完成。
bin log
Binglog是二进制日志文件,主要用于数据的复制、同步等操作,然后以二进制的形式存储在binlog中,二进制文件记录了所有mySQL修改数据库的操作,其中还包括每条语句执行时间、占用资源(线程ID等)等信息,在默认情况下binlog是开启的,如果想要开启可以在启动mySQL服务的时候使用--log-bin[=filename]实现。也可以在my.ini或者my.cnf里修改配置项log-bin来实现。
Undo log
Undolog是什么
重做日志记录的事务的行为,任何修改数据库的操作,都会对应的记入Undolog中,可以通过它将数据回滚到任意历史版本。InnoDB存储引擎中,每条行数据都有隐藏字段,隐藏字段中包含生成本条数据的事务ID和指向Undo的指针。每条数据中都存在这个指向UndoLog的指针,指向更早版本的Undolog,因此构成了一条基于UndoLog的版本链,依靠这条版本链可以将数据恢复成任意历史版本。
Undo log在InnoDB中用来做什么
InnoDB存储引擎依赖Undo实现事务的回滚。 并InnoDB在RR隔离级别下,进行快照读时,采用MVCC的技术就已经解决了幻读问题。MVCC又称多版本并发控制,属于乐观锁的范畴。在进行写时,MVCC机制会将要修改的数据行复制出来,形成一个新的版本,然后用该数据行替换原数据行,原数据行保存到UndoLog中。在进行快照读的时候,根据ReadView和当前事务ID决定哪个版本的数据是可见的,然后依赖基于UndoLog的版本链进行数据的回滚,在不加锁的前提下解决了幻读,具有很好的并发性能。
Redolog 和UndoLog的区别
- Redolog主要用于崩溃恢复,保证数据的持久性。而UndoLog主要进行事务的回滚,保证事务的原子性,以及实现MVCC,提升并发性能。
- Redolog存在于重做日志文件中,iblogfile0,iblogfile1,当重做日志写满,会切换到下一个文件继续写.而UndoLog,位于Undo Segment(段),可以有多个Undo segment,从属于表空间,默认存储于文件ibdata1中,由于InnoDB的索引和表数据也都存在ibdata1这个文件中,当数据过多,UndoLog过多时,可能会使ibdata1文件超过最大文件尺寸,因此可以新建一个表空间,通过修改mySQL默认配置的方式,将UndoLog放在其他的表空间存储。
主动同步是如何实现的
主从复制的工作原理并不复杂,主要分为5个步骤:
- 主服务器将数据更改记录记录到binlog中。
- 从服务器连接上主服务器后,发送一个DUMP(转储)请求。
- 主服务器接收到这个DUMP请求后,开启一个DUMP线程,根据请求中的position,决定从binlog的哪个位置开始进行发送。
- 从服务器开启一个IO线程接收主服务器发送的数据,并将其写入从服务器的中继日志中。
- 从服务器的SQL线程读取并执行中继日志中的内容。