MySQL面试知识整理,Java程序员升职涨薪必备,附赠架构师成长图

1、数据库引擎

MySQL InnoDB MyISAM
事物支持
外键支持
行级 表级
全文索引 是【只支持英文】
崩溃安全恢复支持

选择:MyISAM相对简单,因此在效率上要优于InnoDB。若是系统插入和查询操做多,不须要事务和外键,选择MyISAM,若是须要频繁的更新、删除操做,或者须要事务、外键、行级锁的时候,选择InnoDB。java

2、事务

一、事务特性

  • 原子性(Atomicity):事务做为一个总体被执行 ,要么所有执行,要么所有不执行。事务执行过程当中出错,会回滚到事务开始前的状态,全部的操做就像没有发生同样。
  • 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏,执行结果符合预期规则 。好比A向B转帐,不可能A扣了钱,B却没收到。
  • 隔离性(Isolation):同一时间,只容许一个事务请求同一数据,不一样的事务之间彼此没有任何干扰。好比A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转帐。
  • 持久性(Durability):一个事务一旦提交,对数据库的修改应该永久保存,不能回滚。

二、事物的并发问题

  • 脏读:事务A读取了事务B已经修改但还没有提交的数据。若事务B回滚数据,事务A的数据存在不一致性的问题,那么A读取到的数据就是脏数据。【一致性】
  • 不可重复读:事务A在执行过程当中,第一次读取到的是原始数据,第二次读取到的是事务B已经提交的修改后的数据。致使两次读取同一数据的值不一致。不符合事务的隔离性。【隔离性】
  • 幻读:事务A根据相同条件第二次查询到事务B提交的新增或删除的数据,两次数据结果集不一致。不符合事务的隔离性。【隔离性】

小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住知足条件的行,解决幻读须要锁表。mysql

三、事物的隔离级别

事务隔离级别 读数据一致性 脏读 不可重复读 幻读
读未提交(read-uncommitted) 最低级别
读已提交(read-committed) 语句级
可重复读(repeatable-read) 事务级
串行化(serializable) 最高级别,事务级

四、总结

  • 事务隔离级别为读已提交时,写数据只会锁住相应的行。
  • 事务隔离级别为可重复读时,若检索条件有索引(包括主键索引),默认加锁方式是next-key 锁【间隙锁】;若检索条件没有索引,则更新数据时会锁住整张表。一个间隙被事务加了锁,其余事务是不能在这个间隙插入记录的,这样能够防止幻读。
  • 事务隔离级别为串行化时,读写数据都会锁住整张表
  • 隔离级别越高,越能保证数据的完整性和一致性,可是对并发性能的影响也越大。
  • MySQL默认隔离级别是可重复读。
  • 查看当前数据库的事务隔离级别:show variables like 'tx_isolation';
  • MYSQL MVCC实现机制
  • next-key 锁【间隙锁】

3、锁

​一、行锁

①. 优点ios

  • 锁的粒度小;
  • 发生锁冲突的几率低;
  • 处理并发的能力强。

②. 劣势程序员

  • 开销大;
  • 加锁慢;
  • 会出现死锁。

③. 加锁方式面试

自动加锁。sql

对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及的数据集加排他锁;对于普通SELECT语句,InnoDB不会加任何锁;固然咱们也能够显示的加锁: 加共享锁:select from tableName where ... lock in share mode 加排他锁:select from tableName where ... for update数据库

④. 间隙锁【Next-Key锁】编程

当咱们用范围条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫作"间隙(GAP)"。InnoDB也会对这个"间隙"加锁,这种锁机制就是所谓的间隙锁(Next-Key锁)。数组

危害(坑):若执行的条件范围过大,则InnoDB会将整个范围内全部的索引键值所有锁定,很容易对性能形成影响。缓存

Transaction-A
mysql> update innodb_lock set k=66 where id >=6;
Query OK, 1 row affected (0.63 sec)
mysql> commit;

Transaction-B
mysql> insert into innodb_lock (id,k,v) values(7,'7','7000');
Query OK, 1 row affected (18.99 sec)

⑤. 排他锁

排他锁,也称写锁,独占锁,当前写操做没有完成前,它会阻断其余写锁和读锁。

# Transaction_A
mysql> set autocommit=0;
mysql> select * from innodb_lock where id=4 for update;
+----+------+------+
| id | k    | v    |
+----+------+------+
|  4 | 4    | 4000 |
+----+------+------+
1 row in set (0.00 sec)

mysql> update innodb_lock set v='4001' where id=4;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.04 sec)
# Transaction_B
mysql> select * from innodb_lock where id=4 for update;
+----+------+------+
| id | k    | v    |
+----+------+------+
|  4 | 4    | 4001 |
+----+------+------+
1 row in set (9.53 sec)

⑥. 共享锁

共享锁,也称读锁,多用于判断数据是否存在,多个读操做能够同时进行而不会互相影响。若是事务对读锁进行修改操做,极可能会形成死锁。以下图所示。

# Transaction_A
mysql> set autocommit=0;
mysql> select * from innodb_lock where id=4 lock in share mode;
+----+------+------+
| id | k    | v    |
+----+------+------+
|  4 | 4    | 4001 |
+----+------+------+
1 row in set (0.00 sec)

mysql> update innodb_lock set v='4002' where id=4;
Query OK, 1 row affected (31.29 sec)
Rows matched: 1  Changed: 1  Warnings: 0
# Transaction_B
mysql> set autocommit=0;
mysql> select * from innodb_lock where id=4 lock in share mode;
+----+------+------+
| id | k    | v    |
+----+------+------+
|  4 | 4    | 4001 |
+----+------+------+
1 row in set (0.00 sec)

mysql> update innodb_lock set v='4002' where id=4;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

⑦. 分析行锁定

经过检查InnoDB_row_lock 状态变量分析系统上的行锁的争夺状况,命令:

show status like 'innodb_row_lock%'
mysql> show status like 'innodb_row_lock%';
+-------------------------------+-------+
| Variable_name                 | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0     |
| Innodb_row_lock_time          | 0     |
| Innodb_row_lock_time_avg      | 0     |
| Innodb_row_lock_time_max      | 0     |
| Innodb_row_lock_waits         | 0     |
+-------------------------------+-------+

innodb_row_lock_current_waits: 当前正在等待锁定的数量 innodb_row_lock_time: 从系统启动到如今锁定总时间长度;【很是重要的参数】 innodb_row_lock_time_avg: 每次等待所花平均时间;【很是重要的参数】 innodb_row_lock_time_max: 从系统启动到如今等待最长的一次所花的时间; innodb_row_lock_waits: 系统启动后到如今总共等待的次数;很是重要的参数,直接决定优化的方向和策略。

⑧. 行锁优化

  • 尽量让全部数据检索都经过索引来完成,避免无索引行或索引失效致使行锁升级为表锁。
  • 尽量避免间隙锁带来的性能降低,减小或使用合理的检索范围。
  • 尽量减小事务的粒度,好比控制事务大小,而从减小锁定资源量和时间长度,从而减小锁的竞争等,提供性能。
  • 尽量低级别事务隔离,隔离级别越高,并发的处理能力越低。

二、表锁

①. 优点

  • 开销小;
  • 加锁快;
  • 无死锁。

②. 劣势

  • 锁粒度大;
  • 发生锁冲突的几率高;
  • 并发处理能力低。

③. 加锁方式

自动加锁。

查询操做(SELECT),会自动给涉及的全部表加读锁,更新操做(UPDATE、DELETE、INSERT),会自动给涉及的表加写锁。也能够显示加锁:

共享读锁:lock table tableName read; ​ 独占写锁:lock table tableName write; ​ 批量解锁:unlock tables;

④. 共享读锁

对MyISAM表的读操做(加读锁),不会阻塞其余进程对同一表的读操做,但会阻塞对同一表的写操做。只有当读锁释放后,才能执行其余进程的写操做。

Transaction-A
mysql> lock table myisam_lock read;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from myisam_lock;
9 rows in set (0.00 sec)

mysql> select * from innodb_lock;
ERROR 1100 (HY000): Table 'innodb_lock' was not locked with LOCK TABLES

mysql> update myisam_lock set v='1001' where k='1';
ERROR 1099 (HY000): Table 'myisam_lock' was locked with a READ lock and can't be updated

mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
Transaction-B
mysql> select * from myisam_lock;
9 rows in set (0.00 sec)

mysql> select * from innodb_lock;
8 rows in set (0.01 sec)

mysql> update myisam_lock set v='1001' where k='1';
Query OK, 1 row affected (18.67 sec)

⑤. 独占写锁

对MyISAM表的写操做(加写锁),会阻塞其余进程对同一表的读和写操做,只有当写锁释放后,才会执行其余进程的读写操做。

Transaction-A
mysql> set autocommit=0;
Query OK, 0 rows affected (0.05 sec)

mysql> lock table myisam_lock write;
Query OK, 0 rows affected (0.03 sec)

mysql> update myisam_lock set v='2001' where k='2';
Query OK, 1 row affected (0.00 sec)

mysql> select * from myisam_lock;
9 rows in set (0.00 sec)

mysql> update innodb_lock set v='1001' where k='1';
ERROR 1100 (HY000): Table 'innodb_lock' was not locked with LOCK TABLES

mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)
Transaction-B
mysql> select * from myisam_lock;
9 rows in set (42.83 sec)

小结:表锁,读锁会阻塞写,不会阻塞读。而写锁则会把读、写都阻塞。

⑥. 查看加锁状况:

show open tables; 1表示加锁,0表示未加锁。

mysql> show open tables where in_use > 0;
+----------+-------------+--------+-------------+
| Database | Table       | In_use | Name_locked |
+----------+-------------+--------+-------------+
| lock     | myisam_lock |      1 |           0 |
+----------+-------------+--------+-------------+

⑦. 分析表锁定

经过检查table_locks_waited 和 table_locks_immediate 状态变量分析系统上的表锁定,命令:

show status like 'table_locks%';
mysql> show status like 'table_locks%';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Table_locks_immediate      | 104   |
| Table_locks_waited         | 0     |
+----------------------------+-------+
  • table_locks_immediate: 表示当即释放表锁数。
  • table_locks_waited: 表示须要等待的表锁数。此值越高则说明存在着越严重的表级锁争用状况。

此外,MyISAM的读写锁调度是写优先,这也是MyISAM不适合作写为主的存储引擎。由于写锁后,其余线程不能作任何操做,大量的更新会使查询很可贵到锁,从而形成永久阻塞。

三、什么状况下用表锁?

InnoDB默认采用行锁,在未使用索引字段查询时升级为表锁。MySQL这样设计并非给你挖坑。它有本身的设计目的。 ​ 即使你在条件中使用了索引字段,MySQL会根据自身的执行计划,考虑是否使用索引(因此explain命令中会有possible_key 和 key)。若是MySQL认为全表扫描效率更高,它就不会使用索引,这种状况下InnoDB将使用表锁,而不是行锁。所以,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。

  • 第一种状况:全表更新。事务须要更新大部分或所有数据,且表又比较大。若使用行锁,会致使事务执行效率低,从而可能形成其余事务长时间锁等待和更多的锁冲突。
  • 第二种状况:多表查询。事务涉及多个表,比较复杂的关联查询,极可能引发死锁,形成大量事务回滚。这种状况若能一次性锁定事务涉及的表,从而能够避免死锁、减小数据库因事务回滚带来的开销。

四、总结

  • InnoDB 支持表锁和行锁,使用索引做为检索条件修改数据时采用行锁,不然采用表锁。
  • InnoDB 自动给写操做加锁,读操做不自动加锁。
  • 行锁可能由于未使用索引而升级为表锁,因此除了检查索引是否建立的同时,也须要经过explain执行计划查询索引是否被实际使用。
  • 行锁相对于表锁来讲,优点在于高并发场景下表现更突出,毕竟锁的粒度小。
  • 当表的大部分数据须要被修改,或者是多表复杂关联查询时,建议使用表锁优于行锁。
  • 为了保证数据的一致完整性,任何一个数据库都存在锁定机制。锁定机制的优劣直接影响到一个数据库的并发处理能力和性能。

4、索引

一、原理

咱们拿出一本新华字典,它的目录实际上就是一种索引:非汇集索引。咱们能够经过目录迅速定位咱们要查的字。而字典的内容部分通常都是按照拼音排序的,这实际上又是一种索引:汇集索引。汇集索引这种实现方式使得按主键的搜索十分高效,可是辅助索引搜索须要检索两遍索引:首先检索辅助索引得到主键,而后用主键到主索引中检索得到记录。

二、底层实现【数据结构】

①、mysql主要使用B+树来构建索引,为何不用二叉树和红黑树

  • B+树是多叉的,能够减小树的高度。
  • 索引自己较大,不会所有存储在内存中,会以索引文件的形式存储在磁盘上,因此索引在查找数据的过程当中会涉及到磁盘I/O操做。
  • 因磁盘I/O效率低下,mysql为了尽可能减小磁盘IO的存取次数,须要利用了磁盘存取的局部性原理进行磁盘预读。

    • 局部性原理:为了减小磁盘IO,磁盘每每会进行数据预读,会从某位置开始,预先顺序向后读取必定长度的数据放入内存。由于磁盘顺序读取的效率较高,不须要寻道时间,所以能够提升IO效率。
    • 磁盘预读长度通常为页的整数倍,主存和磁盘以页做为单位交换数据。当须要读取的数据不在内存时,触发缺页中断,系统会向磁盘发出读取磁盘数据的请求,磁盘找到数据的起始位置并向后连续读取一页或几页数据载入内存,而后中断返回,系统继续运行。
  • mysql将B+数的一个节点的大小设为一个页,这样每一个节点只须要一次I/O就能够彻底载入内存【因为节点中有若干个数组,因此地址连续】。
  • 红黑树的结构深度更深,不少逻辑上很近的节点(如父子节点)在物理上可能很远,没法利用局部性原理。

在InnoDB里,每一个页默认16KB,假设索引的是8B的long型数据,每一个key后有个页号4B,还有6B的其余数据(参考《MySQL技术内幕:InnoDB存储引擎》P193的页面数据),那么每一个页的扇出系数为16KB/(8B+4B+6B)≈1000,即每一个页能够索引1000个key。在高度h=3时,s=1000^3=10亿!!也就是说,InnoDB经过三次索引页的I/O,便可索引10亿的key。一般来讲,索引树的高度在2~4。

②. B+Tree与B-Tree的区别

M阶B-Tree

定义:

  • 树中每一个结点至多有m个孩子;
  • 除根结点和叶子结点外,其它每一个结点至少有m/2个孩子;
  • 若根结点不是叶子结点,则至少有2个孩子;
  • 全部叶子结点都在同一层;

特性:

  • 关键字集合分布在整颗树中;
  • 任何一个关键字出现且只出如今一个结点中;
  • 搜索有可能在非叶子结点结束;
  • 其搜索性能等价于在关键字全集内作一次二分查找;
  • 自动层次控制;

M阶B+Tree

定义:

  • 有m个子树的节点包含有m个元素(B-Tree中是m-1);
  • 非叶子节点不保存数据,只用于索引,全部数据都保存在叶子节点中。
  • 全部分支节点和根节点都同时存在于子节点中,在子节点元素中是最大或者最小的元素。
  • 叶子节点会包含全部的关键字,以及指向数据记录的指针,而且叶子节点自己是根据关键字的大小从小到大顺序连接。

特性:

  • 全部关键字都出如今叶子结点的链表中(稠密索引),且链表中的关键字是有序的;
  • 不可能在非叶子结点命中,由于非叶子节点只有Key,没有Data;
  • 非叶子结点至关因而叶子结点的索引(稀疏索引),叶子结点至关因而存储全部关键字数据的数据层;
  • 更适合文件索引系统。

③. B+Tree与B-Tree的区别

  • B+Tree有n棵子树的结点中含有n个关键字; (而B树是n棵子树有n-1个关键字)。
  • B+Tree全部Key(关键字)存储在叶子节点,非叶子节点不存储真正的data(数据)。
  • B+Tree为全部叶子节点增长了一个链指针,且全部叶子节点的关键字按从小到大顺序连接,加强了区间访问性。

④. 为何mysql的索引使用B+树而不是B树呢

  • B+树更适合外部存储(通常指磁盘存储),因为内节点(非叶子节点)不存data(数据)只存Key(关键字),因此B+树一个节点能够存储更多的Key,即每一个节点能索引的范围更大更精确。也就是说使用B+树单次磁盘I/O的信息量相比较B树更大,I/O效率更高。
  • mysql是关系型数据库,常常会按照区间来访问某个索引列,B+树的叶子节点间按Key的顺序创建了链指针,增强了区间访问性,因此B+树对索引列上的区间范围查询很友好。而B树每一个节点的key和data在一块儿,没法进行区间查找。

三、索引优势

  • 快速读取数据。
  • 惟一性索引能保证数据记录的惟一性。
  • 实现表与表之间的参照完整性【经过主键索引】。

四、索引缺点

  • 索引须要占用物理空间。
  • 当对表中的数据进行增长、删除和修改的时候,索引也要动态的维护,下降了数据的维护速度。

5、mysql性能分析

一、MySQL 自身瓶颈

MySQL自身常见的性能问题有磁盘空间不足,磁盘I/O太大,服务器硬件性能低。

  • CPU瓶颈:CPU 饱和通常发生在数据装入内存或从磁盘上读取数据的时候。
  • IO瓶颈:磁盘I/O 瓶颈发生在装入数据远大于内存容量的时候。
  • 服务器硬件的性能瓶颈:可经过top,free,iostat 和 vmstat来查看系统的性能状态。

二、explain 分析sql语句

使用explain关键字能够模拟优化器执行sql查询语句,从而得知MySQL 是如何处理sql语句。

①. id
select 查询的序列号,包含一组能够重复的数字,表示查询中sql语句的执行顺序。通常有三种状况: 第一种:id所有相同,sql的执行顺序是由上至下; 第二种:id所有不一样,sql的执行顺序是根据id大的优先执行(若是是子查询,id的序号会递增); 第三种:id既存在相同,又存在不一样的。先根据id大的优先执行,再根据相同id从上至下的执行。

②. select_type

select 查询的类型,主要是用于区别普通查询,联合查询,嵌套的复杂查询:

  • simple:简单的select 查询,查询中不包含子查询或者union。
  • primary:查询中若包含任何复杂的子查询,最外层查询则被标记为primary。
  • subquery:在select或where 列表中包含了子查询。
  • derived:在from列表中包含的子查询被标记为derived(衍生),MySQL会递归执行这些子查询,把结果放在临时表里。
  • union:若第二个select出如今union以后,则被标记为union【联合查询】,若union包含在from子句的子查询中,外层select将被标记为:derived。
  • union result:从union表获取结果的select。

subquery和union 还能够被标记为dependent和uncacheable。 dependent意味着select依赖于外层查询中发现的数据。 uncacheable意味着select中的某些特性阻止结果被缓存于一个item_cache中。

③. table

查询结果来自于哪一个表。

④. partitions

表所使用的分区,若是要统计十年公司订单的金额,能够把数据分为十个区,每年表明一个区。这样能够大大的提升查询效率。

⑤. type

这是一个很是重要的参数,链接类型,常见的有:all , index , range , ref , eq_ref , const , system , null 八个级别。

性能从最优到最差的排序:null > system > const > eq_ref > ref > range > index > all

对java程序员来讲,若保证查询至少达到range级别或者最好能达到ref则算是一个优秀而又负责的程序员。

  • all:(full table scan)全表扫描无疑是最差,如果百万千万级数据量,全表扫描会很是慢。
  • index:(full index scan)全索引文件扫描比all好不少,毕竟从索引树中找数据,比从全表中找数据要快。
  • range:只检索给定范围的行,使用索引来匹配行。范围缩小了,固然比全表扫描和全索引文件扫描要快。sql语句中通常会有between,>,< 等查询,IN()和OR列表,也会显示range。
  • ref:非惟一性索引扫描,本质上也是一种索引访问,返回全部匹配某个单独值的行。好比查询公司全部属于研发团队的同事,匹配的结果是多个并不是惟一值。
explain select * from t1 where name='yayun';
  • eq_ref:惟一性索引扫描,对于每一个索引键,表中有一条记录与之匹配。好比用主键或惟一字段做为判断条件。
explain select t1.name from t1, t2 where t1.id=t2.id;
  • const:表示经过索引一次就能够找到,const用于比较primary key 或者unique索引。由于只匹配一行数据,因此很快,若将主键至于where列表中,MySQL就能将该查询转换为一个常量。
explain select * from t1 where id = 1
  • system:表只有一条记录(等于系统表),这是const类型的特列,平时不会出现,了解便可。
  • NULL:MySQL在优化过程当中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值能够经过单独索引查找完成。
explain select * from t1 where id = (select min(id) from t2);

⑥. possible_keys

显示查询语句可能用到的索引(即查询涉及字段中存在索引的字段,可能为一个、多个或为null),不必定被查询实际使用,仅供参考使用。

⑦. key

显示查询语句实际使用的索引字段。若为null,则表示没有使用索引。

⑧. key_len

显示索引中使用的字节数,可经过key_len计算查询中使用的索引长度。

​ 在不损失精确性的状况下索引长度越短越好。key_len 显示的值为索引字段的最可能长度,并不是实际使用长度,即key_len是根据表定义计算而得,并非经过表内检索出的。

⑨. ref

表示上述表的链接匹配条件,即哪些列或常量被用于查找索引列上的值。即显示使用哪一个列或常数与key一块儿从表中选择行。

**⑩. rows

根据表统计信息及索引选用状况,大体估算出找到所需的记录所须要读取的行数,值越大越很差。

即根据查询语句及索引选用状况,大体估算出要获得查询结果,所须要在表中读取的行数。

⑪. filtered

一个百分比的值,和rows 列的值一块儿使用,能够估计出查询执行计划(QEP)中的前一个表的结果集,从而肯定join操做的循环次数。小表驱动大表,减轻链接的次数。

⑫. extra

包含不适合在其余列中显示但又十分重要的额外信息。

  • Using filesort: “文件排序”,说明MySQL中没法利用索引完成的排序操做 ,MySQL会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。出现这个就要马上优化sql。
  • Using temporary: 使用了临时表保存中间结果,说明MySQL在对查询结果排序时使用临时表。常见于排序 order by 和 分组查询 group by时,须要借助辅助表再进行排序的状况(这种状况是多个表都涉及到排序字段才会引发的)。 出现这个更要马上优化sql。
  • Using index: 表示相应的select 操做中使用了覆盖索引(Covering index),避免访问了表的数据行,效果不错!若是同时出现Using where,代表索引被用来执行索引键值的查找。若是没有同时出现Using where,表示索引只是用来读取数据而非执行查找动做。 覆盖索引(Covering Index) :也叫索引覆盖,就是select 的数据列只用从索引中就可以取得,没必要读取数据行,MySQL能够利用索引返回select 列表中的字段,而没必要根据索引再次读取数据文件。
  • Using index condition: 在5.6版本后加入的新特性,优化器会在索引存在的状况下,经过符合RANGE范围的条数 和 总数的比例来选择是使用索引仍是进行全表遍历。
  • Using where: 代表使用了where 过滤。
  • Using join buffer: 代表使用了链接缓存。
  • impossible where: where 语句的值老是false,不可用,不能用来获取任何元素。
  • distinct: 优化distinct操做,在找到第一匹配的元组后即中止找一样值的动做。

三、explain总结

经过explain的参数介绍,咱们能够得知:

  • sql的查询顺序(根据id,id越大越先执行)。
  • 数据读取操做的操做类型(type)
  • 哪些索引被实际使用(key)
  • 表之间的引用(ref)
  • 每张表有多少行被优化器查询(rows)

6、数据库性能优化

一、优化思路

可从如下几个方面对数据库性能进行优化:

  • 优化数据库与索引的设计。
  • 优化SQL语句。
  • 加缓存【Memcached, Redis】
  • 主从复制,读写分离。
  • 垂直拆分,其实就是根据你模块的耦合度,将一个包含多个字段的表分红多个小的表,将一个大的系统分为多个小的系统,也就是分布式系统。
  • 水平切分,针对数据量大的表,这一步最麻烦,最能考验技术水平,要选择一个合理的sharding key,为了有好的查询效率,表结构也要改动,作必定的冗余,应用也要改,sql中尽可能带sharding key,将数据定位到限定的表上去查,而不是扫描所有的表;

二、数据库与索引设计

①. 数据库设计

  • 表字段避免null值出现,null值很难进行查询优化且占用额外的索引空间,推荐默认数字0代替null。
  • 尽可能使用INT而非BIGINT,若是非负则加上UNSIGNED(这样数值容量会扩大一倍),固然能使用TINYINT、SMALLINT、MEDIUM_INT更好。
  • 使用枚举或整数代替字符串类型。
  • 尽可能使用TIMESTAMP而非DATETIME。
  • 单表不要有太多字段,建议在20之内。
  • 用整型来存IP。【可去搜索IP地址转为整型】

等。

②. 索引设计

  • 索引要占用物理内存,并非越多越好,要根据查询有针对性的建立,考虑在WHERE和ORDER BY命令上涉及的列创建索引,可根据EXPLAIN来查看是否用了索引仍是全表扫描。
  • 应尽可能避免在WHERE子句中对字段进行NULL值判断,不然将致使引擎放弃使用索引而进行全表扫描。
  • 值分布很稀少的字段不适合建索引,例如"性别"这种只有两三个值的字段。
  • 字符字段只建前缀索引。
  • 字符字段最好不要作主键。
  • 不用外键,由程序保证约束。
  • 尽可能不用UNIQUE,由程序保证约束。
  • 使用多列索引时主意顺序和查询条件保持一致,同时删除没必要要的单列索引。

③. 总结

就一句话:使用合适的数据类型,选择合适的索引:

  • 使用合适的数据类型

    • 使用可存下数据的最小的数据类型,整型 < date,time < char,varchar < blob
    • 使用简单的数据类型,整型比字符处理开销更小,由于字符串的比较更复杂。如,int类型存储时间类型,bigint类型转ip函数。
    • 使用合理的字段属性长度,固定长度的表会更快。使用enum、char而不是varchar。
    • 尽量使用not null定义字段。
    • 尽可能少用text,非用不可最好分表。
  • 选择合适的索引列(即哪些列适合添加索引)

    • 查询频繁的列,在where,group by,order by,on从句中出现的列。
    • where条件中<,<=,=,>,>=,between,in,以及like 字符串+通配符(%)出现的列。
    • 长度小的列,索引字段越小越好,由于数据库的存储单位是页,一页中能存下的数据越多越好。
    • 离散度大(不一样的值多)的列,放在联合索引前面。查看离散度,经过统计不一样的列值来实现,count越大,离散程度越高。

三、sql优化

  • 使用limit对查询结果的记录进行限定。
  • *避免select ,将须要查找的字段列出来。
  • 使用链接(join)来代替子查询。
  • 拆分大的delete或insert语句。
  • 可经过开启慢查询日志来找出较慢的SQL。如何开启mysql慢查询日志?
  • 不作列运算:SELECT id WHERE age + 1 = 10,任何对列的操做都将致使表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽量将操做移至等号右边。
  • sql语句尽量简单:一条sql只能在一个cpu运算;大语句拆小语句,减小锁时间;一条大sql能够堵死整个库。
  • OR改写成IN:OR的效率是O(n)级别,IN的效率是O(logn)级别,in的个数建议控制在200之内。
  • 不用函数和触发器,在应用程序实现。
  • 避免%xxx式查询。
  • 少用JOIN。
  • 使用同类型进行比较,好比用'123'和'123'比,123和123比。
  • 尽可能避免在WHERE子句中使用 != 或 <> 操做符,不然致使引擎放弃使用索引而进行全表扫描。
  • 对于连续数值,使用BETWEEN不用IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5。
  • 列表数据不要拿全表,要使用LIMIT来分页,每页数量也不要太大。

四、分区、分库、分表

①. 分区

把一张表的数据分红N个区块,在逻辑上看最终只是一张表,但底层是由N个物理区块组成的,经过将不一样数据按必定规则放到不一样的区块中提高表的查询效率。

②. 分表

  • 水平分表:为了解决单表数据量过大(数据量达到千万级别)问题。因此将固定的ID hash以后mod,取若0~N个值,而后将数据划分到不一样表中,须要在写入与查询的时候进行ID的路由与统计。
  • 垂直分表:为了解决表的宽度问题,同时还能分别优化每张单表的处理能力。因此将表结构根据数据的活跃度拆分红多个表,把不经常使用的字段单独放到一个表、把大字段单独放到一个表、把常用的字段放到一个表。

③. 分库

面对高并发的读写访问,当数据库没法承载写操做压力时,无论如何扩展slave服务器,此时都没有意义了。所以需对数据库进行拆分,从而提升数据库写入能力,这就是分库。

④. 问题

  • 事务问题。在执行分库以后,因为数据存储到了不一样的库上,数据库事务管理出现了困难。若是依赖数据库自己的分布式事务管理功能去执行事务,将付出高昂的性能代价;若是由应用程序去协助控制,造成程序逻辑上的事务,又会形成编程方面的负担。
  • 跨库跨表的join问题。在执行了分库分表以后,难以免会将本来逻辑关联性很强的数据划分到不一样的表、不一样的库上,咱们没法join位于不一样分库的表,也没法join分表粒度不一样的表,结果本来一次查询可以完成的业务,可能须要屡次查询才能完成。
  • 额外的数据管理负担和数据运算压力。额外的数据管理负担,最显而易见的就是数据的定位问题和数据的增删改查的重复执行问题,这些均可以经过应用程序解决,但必然引发额外的逻辑运算。

7、缓存

对于不少的数据库系统都可以缓存执行计划,对于彻底相同的sql, 可使用已经已经存在的执行计划,从而跳过解析和生成执行计划的过程。MYSQL提供了更为高级的查询结果缓存功能,对于彻底相同的SQL (字符串彻底相同且大小写敏感) 能够执行返回查询结果。

MySQL缓存机制简单的说就是缓存sql文本及查询结果,若是运行相同的sql,服务器直接从缓存中取到结果,而不须要再去解析和执行sql。若是表更改了,那么使用这个表的全部缓冲查询将再也不有效,查询缓存值的相关条目被清空。更改指的是表中任何数据或是结构的改变,包括INSERT、UPDATE、 DELETE、TRUNCATE(截断)、ALTER TABLE、DROP TABLE或DROP DATABASE等,也包括那些映射到改变了的表的使用MERGE表的查询。显然,这对于频繁更新的表,查询缓存是不适合的,而对于一些不常改变数据且有 大量相同sql查询的表,查询缓存会节约很大的性能。

8、面试真题

问:有个表特别大,字段是姓名、年龄、班级,若是调用select * from table where name = xxx and age = xxx该如何经过创建索引的方式优化查询速度

答:因为mysql查询每次只能使用一个索引,若是在name、age两列上建立复合索引的话将带来更高的效率。若是咱们建立了(name, age)的复合索引,那么其实至关于建立了(name)、(name, age)两个索引,这被称为最佳左前缀特性。所以咱们在建立复合索引时应该将最经常使用做限制条件的列放在最左边,依次递减。其次还要考虑该列的数据离散程度,若是有不少不一样的值的话建议放在左边,name的离散程度也大于age。

问:max(xxx)如何用索引优化

答:在xxx列上创建索引,由于索引是B+树顺序排列的,锁在下次查询的时候就会使用索引来查询到最大的值是哪一个。

问:如何对分页进行优化

答:SELECT * FROM big_table order by xx LIMIT 1000000,20,这条语句会查询出1000020条的全部数据而后丢弃掉前1000000条,为了不全表扫描的操做,在order by的列上加索引就能经过扫描索引来查询。可是这条语句会查询仍是会扫描1000020条,还能改进成select id from big_table where id >= 1000000 order by xx LIMIT 0,20,用ID做为过滤条件将不须要查询的数据直接去除。

9、读者福利

针对Java程序员,笔者最近整理了一份完整的一线互联网大厂面试真题,包含了Kafka、Mysql、Tomcat、Docker、Spring、MyBatis、Nginx、Netty、Dubbo、Redis、Netty、Spring cloud、分布式、高并发、性能调优、微服务等架构技术

须要的朋友请点击下方传送门免费领取

传送门

如下是部分面试题截图

10、架构师成长路线图

相关文章
相关标签/搜索