封锁是数据库原理范畴内的概念,封锁分为排它锁、共享锁、活锁、死锁这4种,以及封锁协议定义了数据库如何使用这些锁来保证并发安全。 所谓封锁就是事务在对某个数据对象例如表、记录等操作之前,先向系统发出请求对其加锁。 加锁后事务 T 就对该数据对象有了一定的控制,在事务 T 释放它的锁之前,其他事务不能更新此数据对象。例如,事务 T1 要修改 A,若在读出 A 之前先锁住 A,其他事务就不能再读取和修改 A 了,直到 T1 修改并写回 A 后解除了对 A 的封锁为止。这样,就不会丢失 T1 的修改。 确切的控制由封锁的类型决定。基本的封锁类型有两种:排他锁(exclusive locks,简称 X 锁)和共享锁(share locks,简称 S 锁)
**X 锁(排他写锁)**:若事务 T1 对数据对象 A 加上 X 锁,则只允许 T 读取和修改 A,其他任何事物都不能再对 A 加任何类型的锁,直到 T 释放 A 上的锁为止。这就保证了其他事务在 T 释放 A 上的锁之前不能再读取和修改 A;
**S 锁(共享读锁)**:若事务 T 对数据 A 加上 S 锁,则事务 T 可以读 A 但是不能修改 A,其他事务只能对 A 加 S 锁而不能加 X 锁,直到 T 释放 A 上的 S 锁为止。这就保证了其他食物可以读 A,但在 T 释放 A 上的 S 锁之前不能对 A 进行任何修改。
封锁种类
排它锁(X锁) 可读可写,一个事务对表加了X锁,其他事务必须等该事务操作完这张表后,才可以对这张表操作。 如果一个事务给表加了 X 锁(意味着该事务要独占这个表),那么: 别的事务不可以继续获得该表的 S 锁 别的事务不可以继续获得该表中的某些记录的 S 锁 别的事务不可以继续获得该表的 X 锁 别的事务不可以继续获得该表中的某些记录的 X 锁
共享锁(S锁) 只读,多个事务可以同时对都某一张表加共享锁 如果一个事务给表加了 S 锁,那么: 别的事务可以继续获得该表的 S 锁 别的事务可以继续获得该表中的某些记录的 S 锁 别的事务不可以继续获得该表的 X 锁 别的事务不可以继续获得该表中的某些记录的 X 锁
封锁协议
封锁有 3 级的封锁协议:
一级封锁协议 事务 T 在对数据对象 A 进行修改之前,必须对其加 X 锁,直至事务结束才释放。事务结束包括正常结束(COMMIT)和非正常结束(ROLLBACK); 在一级加锁协议中,如果仅仅是对数据进行读操作而不进行修改,是不需要进行加锁的。所以只能避免修改丢失而不能避免不可重复读和脏读。
二级封锁协议 在一级加锁协议的基础上增加事务 T 在读取数据 R 之前必须先对其加 S 锁,读完后即可释放 S 锁; 二级加锁协议除防止了丢失修改,还可进一步防止读脏数据。例如:事务 T1 正在对数据对象 R 进行修改,此前已经对 R 加上了 X 锁,此时事务 T2 想读取 R,就必须对 R 加上 S 锁,但是 T2 发现 R 已经被 T1 加上了 X 锁,于是 T2 只能等待 T1 释放了在 R 上加的锁之后才能对 R 加 S 锁并读取。这能防止 T2 读取到 T1 未提交的数据,从而避免了脏读。 但是在二级封锁协议中,由于读完数据后即可释放 S 锁,所以它不能保证可重复读。
三级封锁协议 三级封锁协议是指,在一级封锁协议的基础上增加事务 T 在读取数据 R 之前对其加 S 锁直至事务结束才释放。 三级封锁协议除了防止丢失修改和读“脏”数据之外,还进一步防止了不可重复读。 上述三级协议的主要区别在于什么操作需要申请加锁,以及何时释放锁(即锁的持有时间)。不同的封锁协议使事务达到的一致性是不同的,封锁协议越高,一致性程度越强。
MySQL 中的锁主要分为闩锁(latch)和锁(lock)。 latch对象是除了数据库对象外的其他对象,包括操作缓冲池汇总的LRU列表、删除、添加、移动LRU列表中的元素,为了保证一致性必须要有锁介入,因此引入了latch锁。latch是轻量级的锁,因为它要求锁定的时间必须非常短,主要用于保护临界资源的线程安全。 lock对象是事务,用来锁定数据库中的对象,如表、行、页。一般lock的对象仅在事务commit或rollback后进行释放。lock有死锁机制。
该锁的官方类型名为 LOCK_REC_NOT_GAP。 和前面提到的表锁一样,分 S 锁和 X 锁,只是作用粒度精确到行了。
Gap Locks(间隙锁)
该锁的官方类型名为 LOCK_GAP。 MySQL 解决幻读问题有两种方案: 第一种是 MVCC,因为新插入的数据事务 ID 必然不在 ReadView 内,因此读取这些记录后会被直接忽略,但是快照读只在普通读操作中生效,如果发生了当前读仍然会有幻读问题; 第二种是加锁,但是加锁有一个问题,就是事务没法给尚不存在的记录加锁。 如果我们希望为 number 值为 8 的记录加 gap 锁,则该记录的前后间隙都不允许别的事务立即插入记录: 如图中为 number 值为 8 的记录加了 gap 锁,意味着不允许别的事务在 number 值为 8 的记录前边的间隙插入新记录,其实就是 number 列的值(3, 8)这个区间的新记录是不允许立即插入的。比方说有另外一个事务再想插入一条 number 值为 4 的新记录,它定位到该条新记录的下一条记录的 number 值为 8,而这条记录上又有一个 gap 锁,所以就会阻塞插入操作,直到拥有这个 gap 锁的事务提交了之后,number 列的值在区间(3, 8)中的新记录才可以被插入。 另外,如何为(20, +∞)这个区间加 gap 锁?其实是为数据页中的 Infimum 和 Supremum 记录加上了 gap 锁。 比如假设此时表里有 5、25 这两条数据,则SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE查询 10 到 20 范围内的记录,并加上范围(5, 10)、[10, 20]、(20, 25]以内的 gap 锁。 比如假设此时表里有 102、105、107 三个值,则select * from test where n = 105 for update;这个语句会对(102, 105)、(105, 107]这两个区间加 gap 锁。
间隙锁可以通过innodb_locks_unsafe_for_binlog参数来开启关闭:
设置为ON,表示关闭区间锁,此时一致性会被破坏(所以是unsafe)
设置为OFF,表示开启区间锁
Next-Key Locks
该锁的官方类型名为 LOCK_ORDINARY。 Next-Key Lock 其实是Record Lock 和 Gap Lock 的组合,它既会保护该条记录,又能阻止别的事务将新记录插入被保护记录的前后间隙。
意向锁
加表锁时怎么知道该表上有没有行锁?InnoDB 通过意向锁(Intention Locks)来解决这个问题: 1、意向共享锁,英文名:Intention Shared Lock,简称IS 锁。当事务准备在某条记录上加 S 锁时,需要先在表级别加一个 IS 锁。 2、意向独占锁,英文名:Intention Exclusive Lock,简称IX 锁。当事务准备在某条记录上加 X 锁时,需要先在表级别加一个 IX 锁。
IS、IX 锁是表级锁,它们的提出仅仅为了在之后加表级别的 S 锁和 X 锁时可以快速判断表中的记录是否被上锁,以避免用遍历的方式来查看表中有没有上锁的记录,也就是说其实 IS 锁和 IX 锁是兼容的,IX 锁和 IX 锁是兼容的。
一般来说间隙锁可以避免 gap 锁锁住的区间被其他事务修改(当要插入的记录所在的区间有 gap 锁,事务会先再该间隙上加一个插入意向锁),但是还有一种情况正好是反过来的:如果一个事务首先插入了一条记录,别的记录如果直接读(SELECT … LOCK IN SHARE MODE 或 SELECT … FOR UPDATE)则会产生脏读问题,如果直接修改则又会产生脏写问题。 这个问题在 InnoDB 中是通过事务 ID 解决的:
聚簇索引中有一个隐藏列 trx_id,存储的是最后改动该记录的事务 ID,新插入记录的 trx_id 当然就是当前事务的事务 ID,如果别的事务想对该记录添加 S 锁或 X 锁,会首先看一下该记录 trx_id 是否是当前正活跃的事务,如果是的话就会创建一个 X 锁然后进入等待状态;
读-读的情况并不会引起并发冲突,我们不希望对这种情况造成影响,因此 MySQL 给锁分了几个类: 1、共享锁(Shared Locks、S 锁):在事务要读取一条记录时,需要先获取该记录的 S 锁。 2、独占锁(排他锁、Exclusive Locks、X 锁):在事务要改动一条记录时,需要先获取该记录的 X 锁。
兼容性
X
S
X
不兼容
不兼容
S
不兼容
兼容
一般读取一条记录时我们会获取这条记录的 S 锁,但是如果我们想在读取记录时就获取记录的 X 锁,来禁止别的事务读写该记录,则需要使用一些特殊的 SELECT 语句格式: 1、对读取的记录加 S 锁
1
SELECT ... LOCK IN SHARE MODE;
上面语句为记录加 S 锁,允许多个事务同时发起读请求,但是当别的事务尝试获取 X 锁(SELECT … FOR UPDATE 或修改这些记录)则会阻塞,直到当前事务提交之后将这些记录上的 S 锁释放掉。 2、对读取的记录加 X 锁
1
SELECT ... FOR UPDATE;
上面语句为记录加 X 锁,之后别的事务来获取该记录的 S 锁或 X 锁时都会被阻塞,直到当前事务提交之后将这些记录上的 X 锁释放掉。
写操作(write)如何利用锁
DELETE
DELETE 操作会先在 B+树中定位到这条记录,然后获取这条记录的 X 锁,并执行 delete mark 操作(逻辑删除)。
UPDATE
如果未修改该记录的键值并且被更新的列占用的存储空间在修改前后未发生变化,则先在 B+树种定位到这条记录然后获取该记录的 X 锁,最后在原记录的位置执行修改操作。 如果未修改该记录的键值并且至少有一个被更新的列占用的存储空间在修改前后发生变化,则先在 B+树种定位到这条记录后获取 X 锁,将这条记录彻底删除(移入垃圾链表),最后插入一条记录。 如果修改了该记录的键值,则相当于在原记录上 DELETE 后 INSERT。
因此 Session B 要往这个间隙中插入 id = 8 会被锁住,但是 Session C 修改 id = 10 这行是可以的。
例 2、主键不明确导致锁表
下面的情况不会锁表:
1 2 3 4 5 6
明确指定主键,并且有此行数据,row lock SELECT * FROM products WHERE id = '3' FOR UPDATE; SELECT * FROM products WHERE id = '3' and type = 1 FOR UPDATE; 明确指定主键,若查无此行数据,无lock SELECT * FROM products WHERE id = '-1' FOR UPDATE;
下面的情况会锁表:
1 2 3 4 5 6
非索引字段,table lock SELECT * FROM products WHERE name = 'Mouse' FOR UPDATE; 主键不明确,table lock SELECT * FROM products WHERE id <> '3' FOR UPDATE; 主键不明确,table lock SELECT * FROM products WHERE id LIKE '3' FOR UPDATE;
顺序封锁法:顺序封锁法是预先对数据对象规定一个封锁顺序,所有事务都按这个顺序实施封锁。例如在 B 树结构的索引中,可规定封锁的顺序必须是从根节点开始,然后是下一级的子节点,逐级封锁。顺序封锁法可以有效地避免死锁,但是要实现顺序封锁法十分的困难,因为很难事先确定每一个事务要封锁哪些对象,因此也就很难按规定的顺序去实施加锁。
MySQL有死锁检测机制,因此B和C中有一个会插入成功,而另一个事务会自动放弃,并报错:Error Code: 1213. Deadlock found when trying to get lock; try restarting transaction
死锁示例3 - 并发间隙锁的死锁
1 2 3 4 5 6 7 8
A:set session autocommit=0; A:start transaction; A:delete from t where id=6; B:set session autocommit=0; B:start transaction; B:delete from t where id=7; A:insert into t values(5); B:insert into t values(8);
insert into t values(1,1,'2018-11-13'); insert into t values(2,2,'2018-11-12'); insert into t values(3,3,'2018-11-11'); insert into t values(4,4,'2018-11-10'); insert into t values(5,5,'2018-11-09');
delete from t /*comment*/ where a>=4 and t_modified<='2018-11-10' limit 1;
MySQL 额外创建了几个数据库来保存一些系统信息: 1、mysql 这个数据库贼核心,它存储了 MySQL 的用户账户和权限信息,一些存储过程、事件的定义信息,一些运行过程中产生的日志信息,一些帮助信息以及时区信息等。 2、information_schema 这个数据库保存着 MySQL 服务器维护的所有其他数据库的信息,比如有哪些表、哪些视图、哪些触发器、哪些列、哪些索引吧啦吧啦。这些信息并不是真实的用户数据,而是一些描述性信息,有时候也称之为元数据。 3、performance_schema 这个数据库里主要保存 MySQL 服务器运行过程中的一些状态信息,算是对 MySQL 服务器的一个性能监控。包括统计最近执行了哪些语句,在执行过程的每个阶段都花费了多长时间,内存的使用情况等等信息。 4、sys 这个数据库主要是通过视图的形式把 information_schema 和 performance_schema 结合起来,让程序员可以更方便的了解 MySQL 服务器的一些性能信息。
Segment ID(8 字节) 每一个段都有一个唯一的编号,用 ID 表示,此处的 Segment ID 字段表示就是该区所在的段。当然前提是该区已经被分配给某个段了,不然的话该字段的值没啥意义。
List Node(12 字节) 这个部分可以将若干个 XDES Entry 结构串联成一个链表 如果我们想定位表空间内的某一个位置的话,只需指定页号以及该位置在指定页号中的页内偏移量即可。所以: Pre Node Page Number 和 Pre Node Offset 的组合就是指向前一个 XDES Entry 的指针 Next Node Page Number 和 Next Node Offset 的组合就是指向后一个 XDES Entry 的指针。
File Space Header 部分 名称 占用空间大小 描述 Space ID 4 字节 表空间的 ID Not Used 4 字节 这 4 个字节未被使用,可以忽略 Size 4 字节 当前表空间占有的页面数 FREE Limit 4 字节 尚未被初始化的最小页号,大于或等于这个页号的区对应的 XDES Entry 结构都没有被加入 FREE 链表 Space Flags 4 字节 表空间的一些占用存储空间比较小的属性 FRAG_N_USED 4 字节 FREE_FRAG 链表中已使用的页面数量 List Base Node for FREE List 16 字节 FREE 链表的基节点 List Base Node for FREE_FRAG List 16 字节 FREE_FRAG 链表的基节点 List Base Node for FULL_FRAG List 16 字节 FULL_FRAG 链表的基节点 Next Unused Segment ID 8 字节 当前表空间中下一个未使用的 Segment ID List Base Node for SEG_INODES_FULL List 16 字节 SEG_INODES_FULL 链表的基节点 List Base Node for SEG_INODES_FREE List 16 字节 SEG_INODES_FREE 链表的基节点
TODO
表空间配置和优化
表数据存储位置:innodb_file_per_table
这个参数设置为 OFF 表示的是,表的数据放在系统共享表空间,也就是跟数据字典放在一起; 这个参数设置为 ON 表示的是,每个 InnoDB 表数据存储在一个以 .ibd 为后缀的文件中。 建议将这个值设置为 ON,因为,一个表单独存储为一个文件更容易管理,而且在你不需要这个表的时候,通过 drop table 命令,系统就会直接删除这个文件。而如果是放在共享表空间中,即使表删掉了,空间也是不会回收的。
收缩表空间(重建表)
表数据是存储在 B+树的叶子节点(数据页)上的,将一行数据删除置灰将这条记录标记为删除,空间并不会被释放,只有当该页的所有记录都被删除后,该页才会被标记为可复用。 当我们将整个表的数据删除,所有的数据页都会被标记为可复用,但是磁盘上的文件并不会变小,造成空洞。 可以使用alter table A engine=InnoDB命令来重建表,MySQL 会自动完成建立临时表、转存数据、交换表名、删除旧表的操作。 在往临时表写入数据的过程中如果有新数据写入到表 A 的话,就会造成数据丢失,因此在整个 DDL 过程中表 A 不能有更新,这将阻塞正常的数据库语句执行,因此说这个 DDL 不是 Online 的,但是在 MySQL5.6 之后开始引入Online DDL,对这个操作流程做了优化:
建立一个临时文件,扫描表 A 主键的所有数据页;
用数据页中表 A 的记录生成 B+ 树,存储到临时文件中;
生成临时文件的过程中,将所有对 A 的操作记录在一个日志文件(row log)中,对应的是图中 state2 的状态;
临时文件生成后,将日志文件中的操作应用到临时文件,得到一个逻辑数据上与表 A 相同的数据文件,对应的就是图中 state3 的状态;
用临时文件替换表 A 的数据文件。
重建表的过程中,允许对表 A 进行增删改操作,虽然刚开始 DDL 需要拿到 MDL 写锁,但是在真正拷贝数据之前就退化成了读锁,因此并不会阻塞增删改操作。
当根节点中的可用空间用完时继续插入记录,此时会将根节点中的所有记录复制到一个新分配的页,比如页 a 中,然后对这个新页进行页分裂的操作,得到另一个新页,比如页 b。这时新插入的记录根据键值(也就是聚簇索引中的主键值,二级索引中对应的索引列的值)的大小就会被分配到页 a 或者页 b 中,而根节点便升级为存储目录项记录的页。
聚簇索引只能根据主键的查询,如果我们需要根据别的列来查询数据,则必须另外建几棵对应字段的 B+树。 这棵 B+树为字段 a 增加了索引,和之前的聚簇索引的区别包括: 1、页内的记录是按字段 a 排列成一个单向链表; 2、各个存放数据的页也是根据页中记录的 a 列大小顺序排成一个双向链表; 3、存放目录项记录的页分为不同的层次,在同一层次中的页也是根据页中目录项记录的 a 列大小顺序排成一个双向链表。 4、叶子节点存储的并不是完成的数据,而是 a 列+主键这两列的值; 5、目录项记录中不再是主键+页号的搭配,而是 a 列+页号的搭配。 由于叶子节点只存储了字段 a 和主键,如果要获取完整数据,还得根据主键到聚簇索引中再查一遍,这个过程被称为回表。这种按照非主键列建立的 B+树需要一次回表操作才可以定位到完整的记录,所以这种 B+树也被称为二级索引(英文名 secondary index),或者辅助索引。
联合索引
我们也可以同时对多个列建立索引,实际上就是先按字段 a 排序然后再按另一个字段 b 排序,原理与之前的类似。
CREATE TABLE person_info( id INT NOT NULL auto_increment, name VARCHAR(100) NOT NULL, birthday DATE NOT NULL, phone_number CHAR(11) NOT NULL, country varchar(100) NOT NULL, PRIMARY KEY (id), KEY idx_name_birthday_phone_number (name, birthday, phone_number) );
该表有两个索引 1、表中的主键是 id 列,它存储一个自动递增的整数。所以 InnoDB 存储引擎会自动为 id 列建立聚簇索引。 2、显式定义了一个联合索引 idx_name_birthday_phone_number,它由 name、birthday、phone_number 三个字段组成。 画成图其结构大致如下: 下面我们分析不同的查询语句是如何使用这张表里的索引的。
SELECT * FROM person_info WHERE name > 'Asa' AND name < 'Barlow';
因为 B+树索引是按该列值顺序从小到大排序的,因此匹配范围值时,只要分别找到’Asa’和’Barlow’记录,然后通过链表(同一页内是单链表,如果是跨多个数据页则会利用到页之间的双链表)取出它们之间的所有记录,如果是覆盖索引会直接返回,如果是涉及到其他字段则会再回表到聚簇索引中获取完整记录。 需要注意的是,对多个列执行范围查找时,只有对索引最左边那个列进行范围查找时才能用到 B+树索引,因为按 name 列范围查询出的记录并不是按照 birthday 列进行排序的,只有 name 值相同的情况下才会按 birthday 列进行排序,如下所示: +—————-+———–+ | name | birthday | +—————-+———–+ | a | x | | a | y | | b | x | | b | y | | c | z | | … | … | +—————-+———–+
1
SELECT * FROM person_info WHERE name >= 'a' AND name <= 'b' and birthday < 'y';
select * from person_info order by name, birthday desc limit 10;
这样,InnoDB 会先从索引的最左边确定 name 列最小的值,然后找到 name 列等于该值的所有记录,然后从这些记录最右边那条开始往左找 10 条记录;如果不足 10 条,则会继续往右找 name 第二小的记录,以此类推。 这个过程并不能高效利用索引,甚至不如直接利用文件排序。 2、where 子句中出现非排序使用到的索引列
1
select * from person_info where country = 'China' ORDER BY name LIMIT 10;
如上所示,name 字段是有索引的,但是 country 字段没有,因此查询时必须先把符合搜索条件 country=’China’的记录查出再执行排序,这样无法使用索引。 3、排序列包含非同一索引的列 用来排序的多个列不是同一个索引里的,则也不能使用索引来进行排序,原因和上一点其实差不多。 4、排序子句使用了复杂表达式 比如:
1
SELECT * FROM person_info ORDER BY UPPER(name) LIMIT 10;
原 name 排序可能是 Bbc、Cbc、abc,但是用 UPPER 计算后可能会变成 abc、bbc、cbc(默认情况下 MySQL 是会忽略大小写的区别的,这里只是作个例子)。 同理,下面这样的表达式也无法利用索引:
1
select * from person_info where grade / 100.0 > 0.8;
文件排序的例子
1 2 3 4 5 6 7 8 9
CREATE TABLE `t` ( `id` int(11) NOT NULL, `city` varchar(16) NOT NULL, `name` varchar(16) NOT NULL, `age` int(11) NOT NULL, `addr` varchar(128) DEFAULT NULL, PRIMARY KEY (`id`), KEY `city` (`city`) ) default charset = utf8mb4 ENGINE=InnoDB;
对下面 select 语句执行 explain,可以发现 Extra 中包含 filesort 选项,表示该查询语句将会使用到临时文件来执行文件排序:
1
explain select city, name,age from t where city='杭州' order by name limit 1000;
可以用下面方法来确定一个排序语句是否使用了临时文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/* 打开optimizer_trace,只对本线程有效 */ SET optimizer_trace='enabled=on';
/* @a保存Innodb_rows_read的初始值 */ select VARIABLE_VALUE into @a from performance_schema.session_status where variable_name = 'Innodb_rows_read';
/* 执行语句 */ select city, name,age from t where city='杭州' order by name limit 1000;
/* 查看 OPTIMIZER_TRACE 输出 */ SELECT * FROM `information_schema`.`OPTIMIZER_TRACE`\G
/* @b保存Innodb_rows_read的当前值 */ select VARIABLE_VALUE into @b from performance_schema.session_status where variable_name = 'Innodb_rows_read';
如果排序字段正好与索引字段一致,则 MySQL 会直接使用索引来进行排序,因为 B+树叶子节点的记录就是按索引定义的顺序来组织的,如果要查询的字段都在索引里面,则我们称该索引为覆盖索引,使用索引排序后可以直接使用索引中的字段返回、不需要再回表了。
分组
1
SELECT name, birthday, phone_number, COUNT(*) FROM person_info GROUP BY name, birthday, phone_number
上面这个语句会先按 name 值进行分组,所有 name 值相同的划分为一组,对 name 值相同的分组内再按 birthday 的值进行分组,以此类推。 如果没有索引,这个分组统计过程全部都需要在内存里实现,而因为 name,birthday,phone_number 这些字段已经有了索引,InnoDB 会直接使用索引列的顺序来进行分组。
回表的代价
1
select * from person_info where name > 'abc' and name < 'def';
覆盖索引 如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为 覆盖索引。 如果可以从索引中获取到所有数据,那么就不需要再去回表了。 覆盖索引必须要存储索引列的值,哈希索引、空间索引和全文索引都不存储列值,MySQL 只能使用 Btree 索引作为覆盖索引。 排序 MySQL 支持两种方式生成排序结果:通过排序操作;通过索引顺序扫描,MySQL 可以使用同一个索引既满足查找又满足排序,只有当索引的列顺序和 Order By 子句的顺序完全一致,并且所有列的排序方向也一样时,才能使用索引进行排序,其中 Order By 子句和查询限制一样,需要满足索引的最左前缀匹配,当前导列为常量时,可以不满足最左前缀的要求。
索引可能会因为删除、页分裂等原因而导致数据页产生空洞,为了消除这些空洞、压缩磁盘空间,就需要重建索引,将原数据按顺序插入。 重建索引时没有必要 drop 后再 add,比如: alter table T drop index k; alter table T add index(k); 因为这两个语句都会将整个索引重建。重建索引可以用以下语句代替: alter table T engine=InnoDB
下面的建表语句中,既然已经有了(a, b)作为主键索引,那么在 c 上加了索引后,就已经包含了 a、b、c 三个字段,为什么还需要创建(c, a)、(c, b)这两个索引
1 2 3 4 5 6 7 8 9 10
CREATE TABLE `geek` ( `a` int(11) NOT NULL, `b` int(11) NOT NULL, `c` int(11) NOT NULL, `d` int(11) NOT NULL, PRIMARY KEY (`a`,`b`), KEY `c` (`c`), KEY `ca` (`c`,`a`), KEY `cb` (`c`,`b`) ) ENGINE=InnoDB;
为了解答这个问题,首先需要理解索引的字段顺序是有意义的,(a, b)表示先按 a 排序,a 值相同的情况下再按 b 进行排序,索引(c, a)是先按 c 排序,再按 a 排序,这实际上和索引(c)是一样的,所以(c, a)是多余的。
收缩表空间
对一个大小为 1TB 的表文件执行alter table t engine=InnoDB重建,为什么会出现占用空间没变小反而变大的情况。 这是因为,在重建表的时候,InnoDB 不会把整张表占满,每个页留了 1/16 给后续的更新用。也就是说,其实重建表之后不是“最”紧凑的,反而引入了一些空洞。
(或直接修改 redis.conf 中 protected-mode 或在运行服务器的配置中加上–protected-mode no 或 Setup a bind address or an authentication password) 然后注释掉 redis.conf 中的 bind 127.0.0.1,或者在后面添上本机的 ip
普通单例连接
1 2 3 4 5
Jedis jedis = new Jedis(host, 6379); jedis.set("name", "bar"); String name = jedis.get("name"); System.out.println(name); jedis.close();
使用连接池连接
1 2 3 4 5 6 7 8 9 10 11 12 13
// 配置连接池 JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(30); // 最大连接数 config.setMaxIdle(2); // 最大连接空闲数 JedisPool pool = new JedisPool(config, host, 6379); // 从连接池中获取连接 Jedis jedis = pool.getResource(); jedis.set("name", "李四"); String name = jedis.get("name"); System.out.println(name); // 关闭,返回到连接池 jedis.close();
当然必须在 Spring 配置文件中引入属性文件扫描标签 4.在 Spring 配置文件中引入 redis 问题 ERR Client sent AUTH, but no password is set 因为 redis 没有配置密码而建立连接时却发送了密码,所以出错,解决办法是在下面 jedisConnectionFactory 标签中去掉 password 属性