1. 主键id出现自增id不连续的原因

MySQL只保证了自增id是递增的,但不保证是连续的。

唯一键冲突:插入的时候先将自增值自增,然后插入,如果唯一键冲突插入失败,自增值不会回滚。
事务回滚:事务执行失败,自增值也不会回滚。
批量插入数据:批量申请自增id的策略会造成id浪费。同一个语句去申请自增id,每次申请到的自增id个数都是上一次的两倍。

2. insert … select

insert … select 是很常见的在两个表之间拷贝数据的方法。你需要注意,在可重复读隔离级别下,这个语句会给select的表里扫描到的记录和间隙加读锁。

而如果insert和select的对象是同一个表,则有可能会造成循环写入。这种情况下,我们需要引入用户临时表来做优化。

insert 语句如果出现唯一键冲突,会在冲突的唯一值上加共享的next-key lock(S锁)。因此,碰到由于唯一键约束导致报错后,要尽快提交或回滚事务,避免加锁时间过长。

3.最快地复制一张表

我们来对比一下这三种方法的优缺点:

物理拷贝的方式速度最快,尤其对于大表拷贝来说是最快的方法。如果出现误删表的情况,用备份恢复出误删之前的临时库,然后再把临时库中的表拷贝到生产库上,是恢复数据最快的方法。但是,这种方法的使用也有一定的局限性:
必须是全表拷贝,不能只拷贝部分数据;
需要到服务器上拷贝数据,在用户无法登录数据库主机的场景下无法使用;
由于是通过拷贝物理文件实现的,源表和目标表都是使用InnoDB引擎时才能使用。

用mysqldump生成包含INSERT语句文件的方法,可以在where参数增加过滤条件,来实现只导出部分数据。这个方式的不足之一是,不能使用join这种比较复杂的where条件写法。

用select … into outfile的方法是最灵活的,支持所有的SQL写法。但,这个方法的缺点之一就是,每次只能导出一张表的数据,而且表结构也需要另外的语句单独备份。

后两种方式都是逻辑备份方式,是可以跨引擎使用的。

4. 权限控制

4.1 创建用户:

create user ‘ua’@‘%’ identified by ‘pa’;

这条语句的逻辑是创建一个用户’ua’@’%’,密码是pa。注意,在MySQL里面,用户名(user)+地址(host)才表示一个用户,因此 ua@ip1 和 ua@ip2代表的是两个不同的用户。

这条命令做了两个动作:

磁盘上,往mysql.user表里插入一行,由于没有指定权限,所以这行数据上所有表示权限的字段的值都是N;
内存里,往数组acl_users里插入一个acl_user对象,这个对象的access字段值为0。

4.2 全局权限

全局权限,作用于整个MySQL实例,这些权限信息保存在mysql库的user表里。如果我要给用户ua赋一个最高权限的话,语句是这么写的:

grant all privileges on . to ‘ua’@‘%’ with grant option;

这个grant命令做了两个动作:

磁盘上,将mysql.user表里,用户’ua’@’%'这一行的所有表示权限的字段的值都修改为‘Y’;
内存里,从数组acl_users中找到这个用户对应的对象,将access值(权限位)修改为二进制的“全1”。

在这个grant命令执行完成后,如果有新的客户端使用用户名ua登录成功,MySQL会为新连接维护一个线程对象,然后从acl_users数组里查到这个用户的权限,并将权限值拷贝到这个线程对象中。之后在这个连接中执行的语句,所有关于全局权限的判断,都直接使用线程对象内部保存的权限位。

基于上面的分析我们可以知道:

grant 命令对于全局权限,同时更新了磁盘和内存。命令完成后即时生效,接下来新创建的连接会使用新的权限。
对于一个已经存在的连接,它的全局权限不受grant命令的影响。

需要说明的是,一般在生产环境上要合理控制用户权限的范围。我们上面用到的这个grant语句就是一个典型的错误示范。如果一个用户有所有权限,一般就不应该设置为所有IP地址都可以访问。

如果要回收上面的grant语句赋予的权限,你可以使用下面这条命令:

revoke all privileges on . from ‘ua’@‘%’;

这条revoke命令的用法与grant类似,做了如下两个动作:

磁盘上,将mysql.user表里,用户’ua’@’%'这一行的所有表示权限的字段的值都修改为“N”;
内存里,从数组acl_users中找到这个用户对应的对象,将access的值修改为0。

4.3 db权限

除了全局权限,MySQL也支持库级别的权限定义。如果要让用户ua拥有库db1的所有权限,可以执行下面这条命令:

grant all privileges on db1.* to ‘ua’@‘%’ with grant option;

基于库的权限记录保存在mysql.db表中,在内存里则保存在数组acl_dbs中。这条grant命令做了如下两个动作:

磁盘上,往mysql.db表中插入了一行记录,所有权限位字段设置为“Y”;
内存里,增加一个对象到数组acl_dbs中,这个对象的权限位为“全1”。

4.4 表权限和列权限

除了db级别的权限外,MySQL支持更细粒度的表权限和列权限。其中,表权限定义存放在表mysql.tables_priv中,列权限定义存放在表mysql.columns_priv中。这两类权限,组合起来存放在内存的hash结构column_priv_hash中。

这两类权限的赋权命令如下:

create table db1.t1(id int, a int);

grant all privileges on db1.t1 to ‘ua’@‘%’ with grant option;
GRANT SELECT(id), INSERT (id,a) ON mydb.mytbl TO ‘ua’@‘%’ with grant option;

跟db权限类似,这两个权限每次grant的时候都会修改数据表,也会同步修改内存中的hash结构。因此,对这两类权限的操作,也会马上影响到已经存在的连接。

正常情况下,grant命令之后,没有必要跟着执行flush privileges命令。

grant语句会同时修改数据表和内存,判断权限的时候使用的是内存数据。因此,规范地使用grant和revoke语句,是不需要随后加上flush privileges语句的。

flush privileges语句本身会用数据表的数据重建一份内存权限数据,所以在权限数据可能存在不一致的情况下再使用。而这种不一致往往是由于直接用DML语句操作系统权限表导致的,所以我们尽量不要使用这类语句。

5. 分区表

分区表:
MySQL在第一次打开分区表的时候,需要访问所有的分区;
在server层,认为这是同一张表,因此所有分区共用同一个MDL锁;
在引擎层,认为这是不同的表,因此MDL锁之后的执行过程,会根据分区表规则,只访问必要的分区。

实际使用时,分区表跟用户分表比起来,有两个绕不开的问题:一个是第一次访问的时候需要访问所有分区,另一个是共用MDL锁。

因此,如果要使用分区表,就不要创建太多的分区。这里有两个问题需要注意:

分区并不是越细越好。实际上,单表或者单分区的数据一千万行,只要没有特别大的索引,对于现在的硬件能力来说都已经是小表了。

分区也不要提前预留太多,在使用之前预先创建即可。比如,如果是按月分区,每年年底时再把下一年度的12个新分区创建上即可。对于没有数据的历史分区,要及时的drop掉。

至于分区表的其他问题,比如查询需要跨多个分区取数据,查询性能就会比较慢,基本上就不是分区表本身的问题,而是数据量的问题或者说是使用方式的问题了。

分区表和手工分表,一个是由server层来决定使用哪个分区,一个是由应用层代码来决定使用哪个分表。因此,从引擎层看,这两种方式也是没有差别的。

其实这两个方案的区别,主要是在server层上。从server层看,我们就不得不提到分区表一个被广为诟病的问题:打开表的行为。

当然,如果你的团队已经维护了成熟的分库分表中间件,用业务分表,对业务开发同学没有额外的复杂性,对DBA也更直观,自然是更好的。