C++面试总结之数据库(二)

1. 数据库优化

实践中,MySQL的优化主要涉及SQL语句及索引的优化、数据表结构的优化、系统配置的优化和硬件的优化四个方面,如下图所示:

(1)SQL语句及索引的优化

<1>SQL语句的优化

  SQL语句的优化主要包括三个问题,即如何发现有问题SQL、如何分析SQL的执行计划以及如何优化SQL。

a. 怎么发现有问题的SQL?(通过MySQL慢查询日志对有效率问题的SQL进行监控)

  MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,具体指运行时间超过long_query_time值的SQL,则会被记录到慢查询日志中。long_query_time的默认值为10,意思是运行10s以上的语句。慢查询日志的相关参数如下所示:

通过MySQL的慢查询日志,我们可以查询出执行的次数多占用的时间长的SQL、可以通过pt_query_disgest(一种mysql慢日志分析工具)分析Rows examine(MySQL执行器需要检查的行数)项去找出IO大的SQL以及发现未命中索引的SQL,对于这些SQL,都是我们优化的对象。

b. 通过explain查询和分析SQL的执行计划

  使用 EXPLAIN 关键字可以知道MySQL是如何处理你的SQL语句的,以便分析查询语句或是表结构的性能瓶颈。通过explain命令可以得到表的读取顺序、数据读取操作的操作类型、哪些索引可以使用、哪些索引被实际使用、表之间的引用以及每张表有多少行被优化器查询等问题。当扩展列extra出现Using filesort和Using temporay,则往往表示SQL需要优化了。

c. SQL语句的优化

优化insert语句:一次插入多值; 

应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描; 

应尽量避免在 where 子句中对字段进行null值判断,否则将导致引擎放弃使用索引而进行全表扫描; 

优化嵌套查询:子查询可以被更有效率的连接(Join)替代; 

很多时候用 exists 代替 in 是一个好的选择。

<2>索引优化

建议在经常作查询选择的字段、经常作表连接的字段以及经常出现在order by、group by、distinct 后面的字段中建立索引。

(2) 数据库表结构的优化

数据库表结构的优化包括选择合适数据类型、表的范式的优化、表的垂直拆分和表的水平拆分等手段。

<1> 选择合适数据类型

使用较小的数据类型解决问题;

使用简单的数据类型(mysql处理int要比varchar容易);

尽可能的使用not null 定义字段;

尽量避免使用text类型,非用不可时最好考虑分表;

<2> 表的范式的优化: 表的设计应该遵循三大范式。

<3> 表的垂直拆分

把含有多个列的表拆分成多个表,解决表宽度问题,具体包括以下几种拆分手段:

把不常用的字段单独放在同一个表中;

把大字段独立放入一个表中;

把经常使用的字段放在一起; 

这样做的好处是非常明显的,具体包括:拆分后业务清晰,拆分规则明确、系统之间整合或扩展容易、数据维护简单。

<4> 表的水平拆分:解决数据表中数据过大的问题

水平拆分每一个表的结构都是完全一致的。一般地,将数据平分到N张表中的常用方法包括以下两种:

对ID进行hash运算,如果要拆分成5个表,mod(id,5)取出0~4个值;

针对不同的hashID将数据存入不同的表中;

问题和挑战:跨分区表的数据查询、统计及后台报表的操作等问题

优点:

表分割后可以降低在查询时需要读的数据和索引的页数,同时也降低了索引的层数,提高查询速度;

表中的数据本来就有独立性,例如表中分别记录各个地区的数据或不同时期的数据,特别是有些数据常用,而另外一些数据不常用。

需要把数据存放到多个数据库中,提高系统的总体可用性(分库,鸡蛋不能放在同一个篮子里)。

(3) 系统配置的优化

操作系统配置的优化:增加TCP支持的队列数

mysql配置文件优化:Innodb缓存池设置(innodb_buffer_pool_size,推荐总内存的75%)和缓存池的个数(innodb_buffer_pool_instances)

(4)硬件的优化

CPU:核心数多并且主频高的

内存:增大内存

磁盘配置和选择:磁盘性能

2. 数据库插入数据时怎么判断是否重复操作

(1)对于主键和唯一索引,可以用IGNORE关键字,遇到重复记录会直接忽略插入记录,返回0。

insert ignore into table_name ('id','name') values (1,'eddy')

(2)replace关键字:REPLACE的运行与INSERT很相像,但是如果旧记录与新记录有相同的值,则在新记录被插入之前,旧记录被删除。REPLACE返回受影响的行数。(delete+insert)

replace into table_name ('id','name') values (1,'eddy')

(3)ON DUPLICATE KEY UPDATE:遇到重复的记录则更新指定的字段。如果行作为新记录被插入,则受影响行的值为1;如果原有的记录被更新,则受影响行的值为2。

insert ignore into table_name ('id','name') values (1,'eddy') on 
duplicate key update id = 100

3. 什么是存储过程?有哪些优缺点?

存储过程(Stored Procedure)是在大型数据库系统中,一组为了完成特定功能的SQL 语句集,存储在数据库中,经过第一次编译后再次调用不需要再次编译,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。

存储过程具有以下特点:

(1)存储过程只在创建时进行编译,以后每次执行存储过程都不需再重新编译,而一般 SQL 语句每执行一次就编译一次,所以使用存储过程可提高数据库执行效率;

(2)当SQL语句有变动时,可以只修改数据库中的存储过程而不必修改代码;

(3)减少网络传输,在客户端调用一个存储过程当然比执行一串SQL传输的数据量要小;

(4)通过存储过程能够使没有权限的用户在控制之下间接地存取数据库,从而确保数据的安全。

4. drop、delete与truncate的区别

(1)Delete用来删除表的全部或者一部分数据行,执行delete之后,用户需要提交(commmit)或者回滚(rollback)来执行删除或者撤销删除,delete命令会触发这个表上所有的delete触发器;

(2)Truncate删除表中的所有数据,这个操作不能回滚,也不会触发这个表上的触发器,TRUNCATE比delete更快,占用的空间更小;

(3)Drop命令从数据库中删除表,所有的数据行,索引和权限也会被删除,所有的DML触发器也不会被触发,这个命令也不能回滚。

因此,在不再需要一张表的时候,用drop;在想删除部分数据行时候,用delete;在保留表而删除所有数据的时候用truncate。

5. 什么叫视图?游标是什么?触发器

视图是一种虚拟的表,通常是有一个表或者多个表的行或列的子集,具有和物理表相同的功能,可以对视图进行增,删,改,查等操作。特别地,对视图的修改不影响基本表。相比多表查询,它使得我们获取数据更容易。

游标(cursor)是一个存储在MySQL服务器上的数据库查询,它不是一条 SELECT语句,而是被该语句检索出来的结果集。一般不使用游标,但是需要逐条处理数据的时候,游标显得十分重要。在存储了游标之后,应用程序可以根据需要滚动或浏览其中的数据。游标主要用于交互式应用,其中用户需要滚动屏幕上的数据,并对数据进行浏览或做出更改。

在操作mysql的时候,我们知道MySQL检索操作返回一组称为结果集的行。这组返回的行都是与 SQL语句相匹配的行(零行或多行)。使用简单的 SELECT语句,例如,没有办法得到第一行、下一行或前 10行,也不存在每次一行地处理所有行的简单方法(相对于成批地处理它们)。有时,需要在检索出来的行中前进或后退一行或多行。这就是使用游标的原因。

触发器是与表相关的数据库对象,在满足定义条件时触发,并执行触发器中定义的语句集合。触发器的这种特性可以协助应用在数据库端确保数据库的完整性。

6. MySQL中的悲观锁与乐观锁的实现(资源并发锁)

(1). 悲观锁

悲观锁的特点是先获取锁,再进行业务操作,即“悲观”的认为所有的操作均会导致并发安全问题,因此要先确保获取锁成功再进行业务操作。

通常来讲,在数据库上的悲观锁需要数据库本身提供支持,即通过常用的select … for update操作来实现悲观锁。当数据库执行select … for update时会获取被select中的数据行的行锁,因此其他并发执行的select … for update如果试图选中同一行则会发生排斥(需要等待行锁被释放),因此达到锁的效果。select for update获取的行锁会在当前事务结束时自动释放,因此必须在事务中使用。

这里需要特别注意的是,不同的数据库对select… for update的实现和支持都是有所区别的,例如oracle支持select for update no wait,表示如果拿不到锁立刻报错,而不是等待,mysql就没有no wait这个选项。另外,mysql还有个问题是: select… for update语句执行中所有扫描过的行都会被锁上,这一点很容易造成问题。因此,如果在mysql中用悲观锁务必要确定使用了索引,而不是全表扫描。

(2). 乐观锁

乐观锁的特点先进行业务操作,只在最后实际更新数据时进行检查数据是否被更新过,若未被更新过,则更新成功;否则,失败重试。乐观锁在数据库上的实现完全是逻辑的,不需要数据库提供特殊的支持。一般的做法是在需要锁的数据上增加一个版本号或者时间戳,然后按照如下方式实现:

1. SELECT data AS old_data, version AS old_version FROM …;
2. 根据获取的数据进行业务操作,得到new_data和new_version
3. UPDATE SET data = new_data, version = new_version WHERE version = old_version
if (updated row > 0) 
    // 乐观锁获取成功,操作完成
else
    // 乐观锁获取失败,回滚并重试

乐观锁是否在事务中其实都是无所谓的,其底层机制是这样:在数据库内部update同一行的时候是不允许并发的,即数据库每次执行一条update语句时会获取被update行的写锁,直到这一行被成功更新后才释放。因此在业务操作进行前获取需要锁的数据的当前版本号,然后实际更新数据时再次对比版本号确认与之前获取的相同,并更新版本号,即可确认这其间没有发生并发的修改。如果更新失败,即可认为老版本的数据已经被并发修改掉而不存在了,此时认为获取锁失败,需要回滚整个业务操作并可根据需要重试整个过程。

(3). 悲观锁与乐观锁的应用场景

一般情况下,读多写少更适合用乐观锁,读少写多更适合用悲观锁。乐观锁在不发生取锁失败的情况下开销比悲观锁小,但是一旦发生失败回滚开销则比较大,因此适合用在取锁失败概率比较小的场景,可以提升系统并发性能。

7. join语句

8. 和数据库移植问题

环境:

A机器:cenos6.5、mysql是5.5 
B机器:cenos7、mysql是5.5

移植前的准备:

(1)找到mysql数据库数据文件的位置:find / -name 'mysql' 。找到所有mysql的目录查看A、B机器的数据文件的位置。 一般
mysql5.5默认的位置是/var/lib/mysql 
(2)查看linux系统中内存大小,是否能符合移植的需要:df -h
(3)初步了解数据文件的格式 
    1)从文件的扩展名是myisam类型的。 
      .frm 是描述表结构的文件 
      .MYD 是表的数据文件 
      .MYI 是表数据文件中任何索引的数据树。 
    2)而有些数据文件没有.MYD是数据存储在ibdata1。 
因此我们所要做的是将整个/var/lib/mysql整个目录拷贝到B机器上

准备移植:

1)首先关闭A、B机器的mysql服务:service mysql stop
2)备份B机器的mysql数据(以防移植后出现问题) 
3)使用scp指令传输文件:scp -r [email protected]:/var/lib/mysql  /var/lib
   其中192.168.1.1是A机器的ip地址。由于文件110G左右,传输速度基本稳定在10M左右,传输时间基本可以接受。 
   Scp –r;递归复制整个目录
4)这个时候从A机器拷贝的mysql 数据文件由于是在root权限下执行的,因此数据文件的权限是root,需要将权限改为mysql
   chown -r mysql:mysql 
5)若复制后的文件中有mysql-bin-xxx格式的文件,会导致启动mysql服务失败,若有,需删除。 
6)确认不缺失mysql数据库的前提下,开启mysql服务:service mysql start

9. 用MySQL语法建一个学生表,包括学生姓名、性别、年龄、班级信息。

CREAT TABLE student (
  ID int primary key not null,
  NAME varchar(50),
  sex int,
  age int,
  classNo int )

10. char()与varchar()的区分,什么情况下用char()?(两者区别很重要)

char的长度是不可变的,以空间换取时间效率,而varchar的长度是可变的,也就是说,定义一个char[10]和varchar[10],如果存进去的是‘csdn’,那么char所占的长度依然为10,除了字符‘csdn’外,后面跟六个空格,而varchar就立马把长度变为4了,取数据的时候,char类型的要用trim()去掉多余的空格,而varchar是不需要的。

char的存储方式是,对英文字符(ASCII)占用1个字节,对一个汉字占用两个字节;而varchar的存储方式是,对每个英文字符占用2个字节,汉字也占用2个字节。

两者的存储数据都非unicode的字符数据。

11. 在sql语句中加上字符集的方法。

在连接数据库之后,读取数据之前,先执行一项查询“SET NAMES UTF8”

mysql_query("set names 'utf8'"); //mysql_query函数来执行传入的sql語句

12. 有A,B用户,从A转100块钱到B用户,写出sql语句,需要注意的问题

start transaction(事务)
update from account set money=money+100 where name='b';
update from account set money=money-100 where name='a';
commit

猜你喜欢

转载自blog.csdn.net/lxin_liu/article/details/89329054