1.动态sql语句# 和$的区别

{ }:解析为一个 JDBC 预编译语句(prepared statement)的参数标记符。

  1. 传入的name参数为Jack
  2. select * from user where name = #{name};
  3. 被解析成
  4. select * from user where name = ?;

而$就是纯粹的字符串替换:

  1. 传入的name参数为Jack
  2. select * from user where name = #{name};
  3. 被解析成
  4. select * from user where name = 'Jack';

$可能导致SQL注入,所以能用#尽量用#
当表名是参数的时候,只能使用$,因为#的占位符会加上’’,导致sql语法错误

mybatis除了#和$,还会进行预编译,sql语句的格式都预先编译好,直接填参数,就不会sql注入了,预编译的底层还是使用PreparedStatement.

2.MySql主从复制

(1)过程

事务提交了之后,binlog会记录事务对事务的更新操作,
slave会通过连接定期询问master的binlog是否更新,是则新建一个IO线程连接到master,master为每个连接到自身的IO线程开启一个dump线程,发送本地binlog的更新部分,slave收到之后保存在中继日志中,并启动一个SQL线程来从中继日志中复制和更新本地数据库

(2)同步复制 异步复制 半同步复制

主要区别是是否受到从库备份成功消息再返回给客户端结果

3.分库分表如何保证主键id唯一性

(1)使用UUID

UUID包括当前日期时间,时钟序列和全网唯一网卡号码,保证唯一性,实现简单
但是无法保证递增性,查询慢,且无意义

(2)使用Snowflake雪花算法

一个snowflake包括64位:
image.png
47位时间戳可以支持68年,10位机器码可以前几位划分机房,后几位划分机器id,12位序列号代表每ms生成1024个(1ms内id从1递增到1024)
具体到业务,可以动态定制,比如将时间戳减小一点儿,增加表示业务号的字段等

开发具体部署
1.直接编入代码中,每个机器设置不同的机器码
2.单独设置机器号的发号服务器

注意事项:
(1)利用系统的对时功能,时间不准的时候停止发号,避免id重复
(2)当并发不高的时候,比如每ms只生成一个id,那么每ms生成的id末位都是1,此时去做分库分表的时候就会导致数据分布不均匀,那么可以使用代码控制,当并发不高的时候,id在1秒内增加而不是1毫秒内

4.MySql的锁机制

(1)行锁,表锁,页锁(锁的粒度)

表锁:innodb和myIsam都支持表锁,优势是加锁快,性能高,但是缺点是并发冲突高
行锁:innodb支持行锁,优势是并发冲突小,缺点是,资源开销较大
页锁:表锁和行锁的一个折中,锁住相邻的几个记录

行锁锁的是什么

innodb的行锁锁住的是索引,如果不通过索引来操作数据,那么innodb使用的还是行锁,Oracle行锁不一样,它是锁住具体数据记录。
如果sql语句操作了主键索引,那么这个主键索引会被锁住,如果操作了非主键索引,那么这个非主键索引和对应的主键索引都会被锁住
例如,只有一个id是主键索引,where查询条件却通过name字段,那么不走索引,会直接锁住表

(2)共享锁和排他锁(锁住内容的共享)

mysql的增删改默认加排他锁,查询不加锁,我们可以指定查询是否加锁,加共享或者排他锁
共享锁可以重复加,但是一旦加了排他锁,就不能再加任何锁
行锁的共享锁:
select… lock in share mode
行锁的排他锁
select… for update
表锁的共享锁
LOCK TABLE table_name [ AS alias_name ] READ
表锁的排他锁
LOCK TABLE table_name [AS alias_name][ LOW_PRIORITY ] WRITE
表锁解锁
unlock tables;

(3)乐观锁和悲观锁

悲观锁就是先获取锁,再访问内容,上面提到的
乐观锁就是通过程序员控制,访问数据前不需要加锁,最典型的就是通过版本号
测试innodb的悲观锁,innodb默认开启自动提交,意思是每执行一个语句,都将其作为一个事务提交,我们要测试一个悲观锁,就要先关闭autocommit,才能看到锁住的效果。

(4)意向锁:包括意向共享锁和意向排他锁

例如,一个支持行锁的拥有100W数据的表,当想要加表锁的时候,需要遍历每个记录是否拥有共享或者排他锁,但是引入了意向锁之后,就可以直接判断整个便是否能加锁

(5)间隙锁

对数据之间的间隙加锁,防止插入操作,幻读就是使用间隙锁解决的,间隙锁是可以享的,即多个事务加的间隙锁可以有重叠区间
下面是间隙锁的案例(id主键,num普通索引)

  1. mysql> select * from product_copy;
  2. +----+--------+-------+-----+
  3. | id | name | price | num |
  4. +----+--------+-------+-----+
  5. | 1 | 伊利 | 68 | 1 |
  6. | 2 | 蒙牛 | 88 | 1 |
  7. | 6 | tom | 2788 | 3 |
  8. | 10 | 优衣库 | 488 | 4 |
  9. +----+--------+-------+-----+
  10. 其中id为主键 num为普通索引
  11. 窗口A
  12. mysql> select * from product_copy where num=3 for update;
  13. +----+------+-------+-----+
  14. | id | name | price | num |
  15. +----+------+-------+-----+
  16. | 6 | tom | 2788 | 3 |
  17. +----+------+-------+-----+
  18. 1 row in set
  19. 窗口B
  20. mysql> insert into product_copy values(5,'kris',1888,2);
  21. 这里会等待 直到窗口A commit才会显示下面结果
  22. Query OK, 1 row affected
  23. 但是下面是不需要等待的
  24. mysql> update product_copy set price=price+100 where num=1;
  25. Query OK, 2 rows affected
  26. Rows matched: 2 Changed: 2 Warnings: 0
  27. mysql> insert into product_copy values(5,'kris',1888,5);
  28. Query OK, 1 row affected

当我们关闭了mysql的自动提交,语句select * from product_copy where num=3 for update;在commit之前是会锁住(1,3】和(3,4】之间的间隙的,因为num为普通索引,在这两个区间内插入num=3的记录可能导致幻读。
可以总结出:
对于主键索引和唯一索引,是不会有间隙锁的,因为查询条件是唯一的,不会幻读,间隙锁只针对insert操作

(6)临键锁

是记录锁和间隙锁的结合,它锁住某个记录和它之前的间隙,之前的间隙锁只能针对insert操作,变成临间锁之后,当前数据也被锁住,就同时解决增加和删除数据导致的幻读了
所有对非唯一索引的操作都是通过临键锁确定的,

(7)死锁

MyIsam不会有死锁,因为是一次获取全部的表锁
Innodb因为是逐步获取锁,所以可能发生死锁
两个事务一个锁定了主键id,在等待给普通索引加锁,另一个先锁定了那个普通索引,在等待给那个主键索引加锁,那么就会导致死锁
image.png
如何解决死锁:
可以尽可能一次锁定所有资源
或者对于很容易死锁的地方直接使用表锁

5.如何预防SQL注入

(1)采用预编译
(2)限制某些特定字符,例如’,%,—
(3)限制输入参数类型,长度

6.Mysql的timestamp和DateTime有什么区别

timestamp占四个字节,DateTime占据八个字节
timestamp范围较小,最多支持到2038

7.char和varchar的区别

char固定长度,varchar可变长度,例如varchar指定长度为10,传入一个’CSDN’,varchar是自动把长度变成4
固定长度的字符串使用char,例如手机号身份证号等
char 中英文分别2和1个字节,varchar中英文都占据2字节