来源 | 科技缪缪
想进大厂,mysql不会那可不行,来接受mysql面试挑战吧,看看你能坚持到哪里?
create table user(id int(11) not null,
age int(11) not null,
primary key(id),key(age)
);
B+树是左小右大的顺序存储结构,节点只包含id索引列,而叶子节点包含索引列和数据,这种数据和索引在一起存储的索引方式叫做聚簇索引,一张表只能有一个聚簇索引。假设没有定义主键,InnoDB会选择一个唯一的非空索引代替,如果没有的话则会隐式定义一个主键作为聚簇索引。
这是主键聚簇索引存储的结构,那么非聚簇索引的结构是什么样子呢?非聚簇索引(二级索引)保存的是主键id值,这一点和myisam保存的是数据地址是不同的。
最终,我们一张图看看InnoDB和Myisam聚簇和非聚簇索引的区别。
explain select * from user where age=1; //查询的name无法从索引数据获取explain select id,age from user where age=1; //可以直接从索引获取
锁的类型有哪些呢
mysql锁分为
共享锁和
排他锁,也叫做读锁和写锁。 读锁是共享的,可以通过lock in share mode实现,这时候只能读不能写。 写锁是排他的,它会阻塞其他的写锁和读锁。从颗粒度来区分,可以分为
表锁和
行锁两种。 表锁会锁定整张表并且阻塞其他用户对该表的所有读写操作,比如alter修改表结构的时候会锁表。 行锁又可以分为
乐观锁和
悲观锁,悲观锁可以通过for update实现,乐观锁则通过版本号实现。
read commit 读已提交,两次读取结果不一致,叫做不可重复读。
不可重复读解决了脏读的问题,他只会读取已经提交的事务。 用户开启事务读取id=1用户,查询到age=10,再次读取发现结果=20,在同一个事务里同一个查询读取到不同的结果叫做不可重复读。repeatable read 可重复复读,这是mysql的默认级别,就是每次读取结果都一样,但是有可能产生幻读。
serializable 串行,一般是不会使用的,他会给每一行读取的数据加锁,会导致大量超时和锁竞争的问题。这时候假设小明去执行查询,此时current_version=3:
select * from user where id<=3;
同时,小红在这时候开启事务去修改id=1的记录,current_version=4:
update user set name='张三三' where id=1;
执行成功后的结果是这样的:
如果这时候还有小黑在删除id=2的数据,current_version=5,执行后结果是这样的。
由于MVCC的原理是查找创建版本小于或等于当前事务版本,删除版本为空或者大于当前事务版本,小明的真实的查询应该是这样:
select * from user where id<=3 and create_version<=3 and (delete_version>3 or delete_version is null);
所以小明最后查询到的id=1的名字还是'张三',并且id=2的记录也能查询到。这样做是 为了保证事务读取的数据是在事务开始前就已经存在的,要么是事务自己插入或者修改的。 明白MVCC原理,我们来说什么是幻读就简单多了。举一个常见的场景,用户注册时,我们先查询用户名是否存在,不存在就插入,假定用户名是唯一索引。
1、小明开启事务current_version=6查询名字为'王五'的记录,发现不存在。
2、小红开启事务current_version=7插入一条数据,结果是这样:
3、小明执行插入名字'王五'的记录,发现唯一索引冲突,无法插入,这就是幻读。
begin;select * from user where age=20 for update;begin;insert into user(age) values(10); #成功insert into user(age) values(11); #失败insert into user(age) values(20); #失败insert into user(age) values(21); #失败insert into user(age) values(30); #失败
只有10可以插入成功,那么因为表的间隙mysql自动帮我们生成了区间(左开右闭)。
(negative infinity,10],(10,20],(20,30],(30,positive infinity)
由于20存在记录,所以(10,20],(20,30]区间都被锁定了无法插入、删除。 如果查询21呢?就会根据21定位到(20,30)的区间(都是开区间)。 需要注意的是唯一索引是不会有间隙索引的。
垂直分表
如果表字段比较多,将不常用的、数据较大的等等做拆分。水平分表
首先根据业务场景来决定使用什么字段作为分表字段(sharding_key),比如我们现在日订单1000万,我们大部分的场景来源于C端,我们可以用user_id作为sharding_key,数据查询支持到最近3个月的订单,超过3个月的做归档处理,那么3个月的数据量就是9亿,可以分1024张表,那么每张表的数据大概就在100万左右。 比如用户id为100,那我们都经过hash(100),然后对1024取模,就可以落到对应的表上了。打宽表,一般而言,商户端对数据实时性要求并不是很高,比如查询订单列表,可以把订单表同步到离线(实时)数仓,再基于数仓去做成一张宽表,再基于其他如es提供查询服务。
ListList>> taskList = Lists.newArrayList();for (int shardingIndex = 0; shardingIndex 1024; shardingIndex++) {
taskList.add(() -> (userMapper.getProcessingAccountList(shardingIndex)));
}List list = null;try {
list = taskExecutor.executeTask(taskList);
} catch (Exception e) {
//do something
}public class TaskExecutor {
public List executeTask(Collection extends Callable> tasks) throws Exception {
List result = Lists.newArrayList();List> futures = ExecutorUtil.invokeAll(tasks);for (Future future : futures) {
result.add(future.get());
}return result;
}
}
由于mysql默认的复制方式是异步的,主库把日志发送给从库后不关心从库是否已经处理,这样会产生一个问题就是假设主库挂了,从库处理失败了,这时候从库升为主库后,日志就丢失了。由此产生两个概念。
全同步复制 主库写入binlog后强制同步日志到从库,所有的从库都执行完成后才返回给客户端,但是很显然这个方式的话性能会受到严重影响。 半同步复制 和全同步不同的是,半同步复制的逻辑是这样,从库写入日志成功后返回ACK确认给主库,主库收到至少一个从库的确认就认为写操作完成。更多阅读推荐
超详细 | 21张图带你领略集合的线程不安全
云起云涌:PaaS 体系架构与运维系统上云实践
该买哪家二手手机呢?程序员爬取京东告诉你!
苹果秋季发布会于9月16日召开;华为搜索业务将在国内亮相;Android 11正式版发布 | 极客头条
腾讯微博即将关停,十年了,你用过吗?