[{"content":"线性表 栈 stack一种 FILO first in last out（先进后出）的数据结构，常用于递归问题求解\n常用场景：数制转换、迷宫求解、树的个数、括号匹配、表达式求值等等\n先采用数组模拟的方式看看栈容器的实现：\nint st[N]; // 这里使用 st[0] (即 *st) 代表栈中元素数量，同时也是栈顶下标 // 压栈 ： st[++*st] = var1; // 取栈顶 ： int u = st[*st]; // 弹栈 ：注意越界问题, *st == 0 时不能继续弹出 if (*st) --*st; // 清空栈 *st = 0; 如何理解栈？\n举个例子，拿字符串的左右括号匹配问题说明（给定一串字符串，只包含[]、{}、()这三种类型的括号，是否合法匹配），这道问题可以依靠栈的思想来解决。\n思路：右括号都需要寻找离他最近的、还没有匹配的左括号，这里栈这个数据容器只存放还没等到右括号的左括号，因为我们习惯于自左向右进行匹配，而偏偏最需要注意的右括号可能都在右侧存放，很多层次的左括号也不能这样扔掉，所以栈就需要这样的存储左括号（栈在这里就是一个等待匹配的清单），如最近一次放进去的左括号应该被第一次遇到的右括号进行匹配（这就是后进先出）\n匹配的过程：只有当关闭括号即右括号出现时，才会进行匹配。有这样的几步\n（1）先看栈是不是为空，若栈为空代表没有可以和当前右括号匹配的左括号，直接就是不合法匹配；栈不空进行下一步\n（2）看栈顶是谁（合法匹配应该为：离当前括号最近的还没匹配的左括号）\n（3）匹配的类型是否配对，配对继续进行后续的比较，否则代表不合法\n（4）重复上述流程，在所有字符遍历过后，若栈不为空，说明没有匹配完（左括号多）代表失败，否则匹配成功\n/** 该函数实现的功能为：给定一个字符串，判断括号是否匹配 **/ bool isValid(string s) { stack\u0026lt;char\u0026gt; st; for(char c : s) { if(c == \u0026#39;(\u0026#39; || c == \u0026#39;[\u0026#39; || c == \u0026#39;{\u0026#39;) st.push(c); else { if(st.empty()) return false; char x = st.top(); if(x == \u0026#39;[\u0026#39; \u0026amp;\u0026amp; c != \u0026#39;]\u0026#39;) return false; if(x == \u0026#39;(\u0026#39; \u0026amp;\u0026amp; c != \u0026#39;)\u0026#39;) return false; if(x == \u0026#39;{\u0026#39; \u0026amp;\u0026amp; c != \u0026#39;}\u0026#39;) return false; st.pop(); } } return (st.empty() ? true : false); } STL 栈的操作\nstack\u0026lt;int\u0026gt; s; // 新建两个栈 st1 和 st2 std::stack\u0026lt;int\u0026gt; st1, st2; // 为 st1 装入 1 st1.push(1); // 将 st1 赋值给 st2 st2 = st1; // 输出 st2 的栈顶元素 cout \u0026lt;\u0026lt; st2.top() \u0026lt;\u0026lt; endl; // 输出: 1 栈的使用都是一种基于角色的转变，比如一开始的括号匹配问题，栈充当 等待匹配 的角色，而进一步一个问题\n相邻重复字符消除：删除字符串中所有相邻重复字符，反复删除直到不能删。如 abbaca → ca 这种栈的角色就是 最近有效状态 栈的存储就是已经“确认保留”的字符（把遍历到的每一个字符进行一个过滤筛选放入栈中）。\n栈进阶：\n（2）栈 = 嵌套结构的上下文\n题目：字符串解码3[a2[c]] → accaccacc\n思维：栈存的是进入内层前的外层状态，在遇到 ] 时恢复\n队列 queue一种 FIFO (first in first out)先进先出的数据结构\nqueue\u0026lt;int\u0026gt; q; // 普通队列 deque\u0026lt;int\u0026gt; q; // 双端队列(存在前后双指针) priority_queue\u0026lt;int,vector\u0026lt;int\u0026gt;,greater\u0026gt; q; // 优先队列 链表 按值传递：int、long、byte、short、char、float、double、bool、string\n按引用传递：自定义类、数组\n按引用传递传递的参数是新拷贝一份地址和原地址指向同一个位置\n单链表定义：节点在内存中存有数据和下一个节点的指针\n双向链表：在单链表基础上，增加一个prior指针指向上一个节点的指针\n在地址划分上，链表分为数据域、指针域\n头节点 指针 指向第一个节点的地址\n链表的基本操作：\n// 头文件 #include \u0026lt;list\u0026gt; // 双向链表 #include \u0026lt;forward_list\u0026gt; // 单向链表 //单向链表 #include \u0026lt;forward_list\u0026gt; std::forward_list\u0026lt;int\u0026gt; fl = {1,2,3}; fl.push_front(0); // O(1) fl.insert_after(fl.begin(), 42); fl.remove(2); // O(n) //双向链表 #include \u0026lt;list\u0026gt; std::list\u0026lt;int\u0026gt; lst = {1,2,3}; lst.push_back(4); lst.insert(std::next(lst.begin()), 42); lst.erase(lst.begin()); 纯 C 实现的链表\n// 数据结构定义 ( 非嵌入式 ) struct node { Type data; struct node* next; }; // 嵌入式 struct node { Type data; struct link_node; // 链表管理器 } struct link_node { struct link_node* pre; struct link_node* next; }; // 操作定义(侵入式为例) // 尾插 #define Add_tail(head,node) \\ (node)-\u0026gt;next = (head);\t\\ (node)-\u0026gt;pre = (head)-\u0026gt;pre;\t\\ (head)-\u0026gt;pre-\u0026gt;next = head;\t\\ (head)-\u0026gt;pre = node; // 头插 #define Add_head(head,node)\t\\ 1、反转链表\n1\u0026mdash;\u0026ndash;\u0026gt; 2 \u0026mdash;\u0026mdash;\u0026gt; 3 ====\u0026gt; 3\u0026mdash;-\u0026gt; 2\u0026mdash;\u0026ndash;\u0026gt; 1\n头节点 变成3 ，1的next指向null\nListNode reverseList(ListNode head){ ListNode pre = null; ListNode next = null; while(head != null){ //next指针往前走 next = head.next; //head下一个指针域指空 head.next = pre; //pre指向head pre = head; //head指向下一个 head = next; } } 串 模式串匹配 BF\nstring s,t; for(int i = 0, j = 0; i \u0026lt; s.size() \u0026amp;\u0026amp; j \u0026lt; t.size();){ if(s[i] == t[j]) { ++i,++j; } else { i = i - j + 2; j = 0; } if(i == s.size() - 1 \u0026amp;\u0026amp; j == t.size() - 1){ // 匹配开始位置 i - t.size(); } } KMP\n树型结构 基本概念 树是由一个或一个以上的节点组成的。树中存在一个特殊节点（树根 // 根节点），每个节点都是由一些数据和指针组合成的记录。每个节点本身也是一种树结构（即此根节点子树），一棵合法的树，节点间可以互相连接，但不能形成无出路的回路。\n基本术语：\n1、度数（degree）：每个节点所有子树的个数，如A的度数为3，树的度【树中节点最大的度数】\n2、层数（level）：设A层数为1，则B、C、D层数为2\n3、高度（height）：树的最大层数，如图高度为4\n4、树叶【或 终端节点】：度数为0的节点就是树叶，如图中K、L、F\n5、非终端节点：树叶以外的节点，A、B、C、D、E、H\n6、有序树【无序树】：树中节点的各子树从左到右为有序树，否则为无序树\n7、森林：n课互不相交树的集合。将一棵树根节点删去就是森林，反之，给n课独立的树加上一个节点，并把这n课树作为该节点的子树，森林就变回了树。\n性质：\n1.树的节点数 = $ m+1$，其中 $m$ 为树的边数\n2.度为 $m$ 的树中第 $i$ 层 最多有 $m^i$-1 个节点\n3.高度为 $h$ 的 $m$ 叉树 最多有 （$m^h$ -1）/（m-1）个节点\n4.具有 $n$ 个节点的 $m$ 叉树 $min(h)=log_m(n*(m-1)+1)$\n二叉树遍历 二叉树的遍历是指 从根结点出发，按照某种次序一次访问二叉树中的所有结点，使得每个结点被访问一次且仅被访问一次\n1、前序：根左右\n先访问根节点，再递归访问左节点，最后递归访问右节点\nvoid dfs(int x) { if (a[x] == -1) { return; } cout \u0026lt;\u0026lt; a[x] \u0026lt;\u0026lt; \u0026#34; \u0026#34;; dfs(2 * x + 1); dfs(2 * x + 2); } 2、中序：左根右\nvoid dfs(int x) { if (a[x] == -1) { return; } dfs(2 * x + 1); printf(\u0026#34;%d \u0026#34;, a[x]); dfs(2 * x + 2); } 3、后序：左右根\nvoid dfs(int x) { if (a[x] == -1) { return; } dfs(2 * x + 1); dfs(2 * x + 2); printf(\u0026#34;%d \u0026#34;, a[x]); } 序列建树 对于给定的遍历序列如：前序 + 中序、后序 + 中序 两种遍历方法可以推出唯一的树，构建方法\n特殊二叉树 满二叉树：深度为 $k$，且节点数为 $2^k-1$ 的二叉树\n完全二叉树：深度为 $k$，有 $n$ 个节点的二叉树，且自上而下，自左至右的每一个节点都与深度为k的满二叉树中编号对应\n完全二叉树性质：\n1、叶子结点只用出现在最下两层 2、最下层的叶子一定集中在左部连续位置 3、倒数两层若有叶子结点，一定都在右部连续位置 4、如果结点度为1，则该结点只有左孩子 5、同样结点数的二叉树，完全二叉树的深度最小 节点 $i$ 父亲节点公式：$（i - 1）/ 2$ ，0 的父亲节点是自己\n左儿子：$2\\times i+1$ 右儿子：$2 \\times i+2$\n哈夫曼树及编码 编码原则：不出现重复前缀，如（01、011 就是重复的前缀）\n哈夫曼编码：在不出现歧义的情况下编出的码长度一定是最短的\n构建哈夫曼树：\n1、统计各个字符出现的次数\n2、将字符的出现次数最少的开始，合并成一个父节点，继续和出现次数第二小的字符合并节点操作……\n3、将边标号（左 0 右 1）\n4、从根节点开始到需要编号的节点经过的路径，如A：01，B：010 ，C：00，D：011\n该字符串的编码：1010110001100（13）可能 一定是最短的编码长度\n出现次数越多的字符编码越短 哈夫曼树不存在奇数度节点 堆（完全二叉树） 大根堆：这个结构中的任何一个子结构最大值都必须在顶部，即最大值为根节点\n从整棵树的最后一颗子树开始调整，每次都让根节点和左右孩子去比较，如果根节点比左右孩子的最大值要小，那么就将这两个值进行交换，然后此时这颗子树变成了大根堆\n小根堆：在这个结构中，改变当前位置的值\n父亲节点：(i - 1) / 2\n左孩子 ： i * 2 - 1 右孩子 ： i * 2 + 1\n//down void InitialHeap(int parent,int end){ //大根堆 int child = parent * 2 + 1; while(child \u0026lt; end){ if(child+1 \u0026lt; end \u0026amp;\u0026amp; a[child+1] \u0026gt; a[child]) child++; if(a[child] \u0026gt; a[parent]){ swap(a[child],a[parent]); parent = child; child = 2 * parent + 1; }else{ break; } } } for(int parent=n-1;parent \u0026gt;= 0; parent--){ InitialHeap(parent,n); } //堆的up void upHeap(int index){ while(heap[index] \u0026gt; heap[(index-1) / 2]) { swap(heap[index],heap[(index-1) / 2]); index = (index - 1) / 2; } } 堆的应用：对顶堆（解决第k小/大的问题）、优先队列（频繁插入且有序的序列）\n对顶堆：可以动态维护一个序列上第k大的数，k值会发生变化。\n对顶堆由一个大根堆和一个小根堆组成，小根堆维护前k大的数（包含第k大），大根堆维护小于第k大的数\n操作：\n1、插入：插入的元素 \u0026gt;= 小根堆堆顶的元素，将该元素插入小根堆，否则插入大根堆\n2、维护：小根堆大小 \u0026gt; k 时，将小根堆堆顶元素取出插入大根堆，直到小根堆元素个数 = k\n​\t小根堆大小 \u0026lt; k 时，从大根堆里取出元素插入小根堆，直到小根堆元素个数 = k\n3、查询：查询第k大的元素，小根堆堆顶元素\n4、删除：删除第k大元素，删除小根堆堆顶元素\npriority_queue\u0026lt;int\u0026gt; a1; // 大根堆 priority_queue\u0026lt;int, vector\u0026lt;int\u0026gt;, greater\u0026lt;int\u0026gt;\u0026gt; a2; // 小根堆 int main(){ int n, k; cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; k; for (int i = 1; i \u0026lt;= n; ++i) { int x; cin \u0026gt;\u0026gt; x; if (a2.empty() || x \u0026gt; a2.top()) { a2.push(x); } else { a1.push(x); } while (a2.size() \u0026gt; k) { a1.push(a2.top()); a2.pop(); } while (a2.size() \u0026lt; k \u0026amp;\u0026amp; !a1.empty()) { a2.push(a1.top()); a1.pop(); } } printf(\u0026#34;%d \u0026#34;, a2.top()); a2.pop(); return 0; } 二叉排序树BST 二叉查找树，又称二叉搜索树。特点为：当前结点值 \u0026gt; 左孩子值，当前结点值 \u0026lt; 右孩子值\n还有一个特性就是 中序遍历 可以得出有序序列\n用处：快速搜索的任务，动态查找\n(4) / \\ (2) (6) / \\ / \\ (1) (3)(5) (8) \\ (9) BST的平均查找长度和 log(n)等量级，但有时需在构建过程中完成 平衡化 处理，成为AVL（实现了AVL的 BST称为 BBST）\n查找：时间复杂度： $O(\\log_2 n)$\n插入数据时保持大小关系，小于自己的放左边，否则放右边 按照中序进行遍历，得到排序结果（DFS 实现） BST的删除，时间复杂度： $O(\\log_2 n)$\n删除的是叶子结点，直接删除即可 若删除的结点只有左子树 / 右子树，直接让存在的左右子树代替 若删除存在左右子树，找直接前驱 或者 后继 替换当前要删除的值，然后删除前驱或后继（原问题 ===\u0026gt; 删除左子树 / 右子树 / 叶子结点） 平衡二叉树 AVL/BBT 左子树和右子树深度之差绝对值不超过1，即 $| d_{l}-d_{r}| \\le1$，其中 d 代表树高或者深度，且它的左右子树也为AVL\n平衡因子(BF)：| 该结点左子树 - 右子树的深度 | ，取值为$[-1,0,1]$\n在构建 AVL 树的过程中，会出现 BF 值不合法情况，需要进行调整，比如\n(4) /* BF = -2，不合法值 */ (6) \\ 进行调整 / \\ (6) ==\u0026gt; (4) (8) / \\ \\ (5) (8) (5) AVL 树存在 4种调整，分别是单向左旋、右旋，双向旋转（先左后右、先右后左）\n判断针对哪一个结点进行旋转操作：找到最小不平衡子树（即以当前结点为根节点时，$|BF| \\ge 2$）\n（1）单向左旋（RR）\n(4) (6) / \\ / \\ (3) (6) ==\u0026gt; (4) (8) / \\ / \\ (5) (8) (3) (5) / \\ // 若在此处插入结点，|BF| \u0026gt;= 2，则需要进行调整，正常4节点要变为6的左孩子，但和原本5冲突，因此冲突的左孩子变为右孩子，如右图 （2）单向右旋（LL）\n(6) (3) / \\ / \\ (3) (8) ==\u0026gt; (4) (6) / \\\t/ \\ (4) (5)\t(5) (8) / \\ // 若在此处插入结点，|BF| \u0026gt;= 2 ，则需要进行调整，正常6节点需要右旋变为3的右孩子，但原本的3存在右孩子，因此冲突的右孩子要变为左孩子，如右图 （3）双向旋转（对失衡节点左孩子先左旋RR，后对失衡结点右旋LL，LR）\n(8) (8) (5) / \\ / \\ / \\ (3) (9) (5) (9) (3) (8) / \\ ===\u0026gt; / \\ ===\u0026gt; / \\ / \\ (2) (5) (3) (6) (2) (4) (6) (9) / \\ / \\ / \\ (4) (6) (2) (4) / \\ /* 在此处插入结点 */ / \\ （4）双向旋转（对失衡节点右孩子先右旋LL，后对失衡节点左旋RR，RL）\n(3) (3) (5) / \\ / \\ / \\ (1) (7) ==\u0026gt; (1) (5) ==\u0026gt; (3) (7) / \\ / \\ / \\ / \\ (5) (8) (4) (7) (1) (4) (6) (8) / \\ / \\ / \\ / \\ (4) (6) (6) (8) / \\ // 若在此处插入结点 接下来还有关于插入结点后进行什么样的平衡化的讨论，即当 BF 值分别在 -1，0，1 时该如何操作\n在插入节点后，可能导致多个祖先结点失衡，只需要调整距离插入结点最近的失衡结点，其他结点自然平衡\n(3) /* BF=-2 */ (3) / \\ / \\ (1) (6) /* BF=-2 (调整该结点)*/ (1) (7) \\ ==\u0026gt; / \\ (7) (6) (9) \\ (9) 删除结点导致失衡，可能导致多结点失衡，需要沿着当前最近失衡结点调整，直到根节点\n// 构建一棵 AVL [ 14 9 5 17 11 12 7 19 16 27] (11) / \\ (7) (17) / \\ / \\ (5) (9) (14) (19) / \\ \\ (12) (16) (27) 针对这个例子，删除结点5\n(11) (11) /* (11)BF=-2 失衡RR型 */ (17) / \\ / \\ / \\ (7) (17) /* (7)BF=-2 失衡RL型 */ (8) (17) (11) (19) \\ / \\ ==\u0026gt; / \\ / \\ / \\ / \\ (9) (14) (19) (7) (9) (14) (19) ==\u0026gt; (8) (14) (16) (27) / / / \\ / / \\ / \\ / \\ (8) (12) (16) (27) (12) (16) (27) (7) (9)(12) (23) / / (23) (23) 红黑树-RB树 背景：由于AVL树在插入时对于\u0026quot;平衡\u0026quot; 概念的严格要求，导致插入时可能效率较低，在实际工程中需要查找效率更高的解决办法，红黑树是一种基于BST和AVL树开发的，结点带有颜色（红、黑）的自平衡二叉排序树。通常 C++ 中的map、multiset，JAVA中的HashMap等数据结构底层为红黑树实现\n性质：\n每个结点的颜色为红或黑的一种 根节点，叶子结点（虚构的外部结点，NULL结点）一定是黑色 任意两个红色结点不能相邻，即红色结点的孩子或父亲一定是黑色结点 从根节点到 叶子节点的每条路径上的黑色节点数量相同 红黑树的查找：和BST一致，时间复杂度 $O(\\log_2n)$\n插入：相较于BST，红黑树需要考虑旋转和颜色问题，一般插入结点时，默认为红色。\n若插入时为空树，插入结点为根节点，将结点染成黑色 若插入为非空树，父亲结点若为黑色，不需要任何操作；若父亲结点为红色，需要考虑插入结点的叔结点 （1）叔结点为红色：叔、爷、父结点变色，爷结点变为cur（变色后有相邻红色结点，以爷结点为基础，递归向上验证）\n(7)B\t(7)R /* cur，当前为根节点再次变色为 B*/ / \\ / \\ (5)R (8)R ===\u0026gt; (5)B (8)B / / (1)R /* 插入结点 cur */ (7)R （2）叔结点为黑色：先通过AVL的四种旋转 LL、LR、RR、RL 以爷结点为准旋转，再将旋转点、旋转中心变色\n注：RL、LR 需先进行一次旋转后，以最后一次的旋转点和旋转中心点为准变色\n// RR型举例 (7)B (9)B \\ / \\ (9)R ===\u0026gt; (7)R (12)R \\ (12)R /*插入结点cur */ // LR型举例 (7)B (5) (5)B / \\ /* 旋转后*/ / \\ /*染色后 */ / \\ (3)R [NULL]B ====\u0026gt; (3) (7) ===\u0026gt; (3)R (7)R \\ (5)R /* 插入结点cur */ 注：图中省略第一次旋转结果，第一次旋转后，是以5为旋转中心，7为旋转点，后续变色以此为基准\n红黑树的删除：\n只有左子树 或 只有右子树，直接使用删除结点的左/右子树代替，代替后颜色变黑（防止违反黑路结点相同性质） 左右子树都存在，找直接前驱或直接后继，用他们代替删除结点，问题转换为删除前驱或后继 没有孩子，对于红结点：不会破坏黑路同结点数目一致性质，可直接删除；对于黑色结点：删除必然违反性质，引入双黑结点的概念（代表该路径缺少一个黑色结点），后面需要对该双黑结点转变为单黑结点 // 删黑色结点，兄弟为黑(兄弟孩子至少一红)，LL型 (7)B (7)B (5)B / \\ / \\ / \\ (5)B [双黑] ==\u0026gt; (5)B [双黑] ==\u0026gt; (2)B (7)B / \\ / \\ / \\ (2)R (6)R (2)B (6)R (6)R [空结点] // 删黑结点，兄弟为黑(兄弟孩子至少一红) LR型 (7)B (7)B (6)R / \\ /*兄孩染色父亲，父亲变黑*/ / \\ /* LR旋转*/ / \\ (5)B [双黑] ==\u0026gt; (5)B [双黑] ==\u0026gt; (5)B (7)B \\ \\ \\ (6)R (6)R [NULL]单黑 // 删黑结点，兄弟为黑，兄弟孩子均为黑（兄弟变红，双黑上移） (15)B (15)B (18)B / \\ /*双黑上移*/ / \\ /*RR型+染色*/ / \\ (8)B (18)B ===\u0026gt; (8)[双黑] (18)B ==\u0026gt; (15)B (27)B / \\ / \\ / / \\ / \\ / \\ (6)B [双黑] (17)B (27)R (6)R (17)B (27)R (8)B (17)B (25)B (34)B / \\ / \\ / (25)B (34)B (25)B (34)B (6)R // 删除黑结点，兄弟为红：兄父变色，父亲朝双黑旋转，保持双黑继续调整 (18)B (18)R (15)B (15)B / \\ / \\ / \\ / \\ (15)R [双黑] ==\u0026gt; (15)B [双黑] ===\u0026gt; (9)B (18)R ===\u0026gt; (9)B (18)R / \\ / \\ / \\ / (9)B (17)B (9)B (17)B (17)B [双黑] (17)R 注：若双黑结点遇到红色结点 或 根节点，可直接变为单黑\nB-Tree B-树 是一种平衡的多路查找树，在文件系统中很常用。对于m阶B-Tree，其有以下这些性质\n有序：结点内有序（任一结点元素，左元素 \u0026lt; 本身 \u0026lt; 右元素） 平衡：所有的叶子结点都在同一层 多路：最多 m 个分支，m - 1 个元素 ​\t$最少 \\begin{cases} 根节点:2分支，1元素 \\\\ 其他结点：\\lceil{\\frac{m}{2}} \\rceil 分支，\\lceil{\\frac{m}{2}} \\rceil-1 \\end{cases}$\nB 树，将每个叶子结点下连接一些空结点（外部结点），而含有数据的结点称为内部结点\n查找：和二叉搜索树类似，首先从根节点开始，依次比较左右子树元素，首先会先将该结点的元素加载到内存中，在该结点内部，可采用线性查找、二分查找的方式（大于当前元素，右移，直到找到不大于的元素查找它的左子树）\n插入：先找到插入元素位置，若插入了没有发生上溢出（结点内元素 \u0026gt; m-1），无需调整；否则以中间元素 $\\lceil{\\frac{m}{2}} \\rceil$ 为分裂点，将分裂点元素上移，两边元素分裂开成为两个独立结点。插入时可能导致一系列的上溢出（直到根节点）\n删除：删除结点类似于BST，中序查找到直接前驱 / 后继，替换完后，问题转换为删除叶子结点。若叶子结点删除没有发生下溢出（结点内元素 \u0026lt; $\\lceil{\\frac{m}{2}} \\rceil-1 $）直接删除，否则查看左右的兄弟是否可以借结点元素（父下，兄弟上），若两边兄弟都不够借，那么将它和左右兄弟之一合并。删除时可能出现一系列下溢出（直到根节点）重复判断即可，注意上层的下溢出调整后，它的左 / 右 子树 需要移动\n$ 叶结点元素\\begin{cases} 没有下溢出：无需调整 \\\\ 下溢出\\begin{cases} 兄弟够借 \\\\ 兄弟不够借：合并(父亲下移到左，然后右合并过来) \\end{cases} \\end{cases}$\n演示文档： B-Tree_BPlusTree_Demo.html\n图状结构 分为：有向图、无向图\n节点的度数 = 入度 + 出度\n入度：指向当前节点的边的数量 出度：当前节点指向其他边的数量\n图的存储 邻接矩阵 有向图：若节点1 连接 节点 2 ，则在矩阵中，第一行第二列标记为1 a[ 1 ] [ 2 ] = 1\n无向图：若节点 1和 2相连，则 a[ 1 ] [ 2 ] = a[ 2 ] [ 1 ] = 1 对称性\n若边带有权值 ， 则在矩阵中 相连的边 设置为他们边的权值\n即若1 、2节点相连，且 且边12所带权值为5，则 a[1] [2] = 5;\n若是两个点之间不存在链接，将该边设置为正无穷表示\n优点：容易判断两个顶点间是否有边，且易计算顶点的度\n缺点：统计边的数量效率较低，空间复杂度高，最多点数量不能超1000\nint spot1,spot2,val; int matrix[500][500]; //带权有向图 for(int i=1;i\u0026lt;=n;i++){ cin \u0026gt;\u0026gt; spot1 \u0026gt;\u0026gt; spot2 \u0026gt;\u0026gt; val; matrix[spot1][spot2] = val; } 邻接表 $O(V + E)$ 空间存图\n邻接矩阵唯一，邻接表不唯一\n利用链表思想实现\n//1、有向图 //遍历每个点的时候，里面都存有长度为n的链表 list\u0026lt;list\u0026lt;int\u0026gt;\u0026gt; graph; //无权图 //空间上一开始链表都是空的，不占用空间，随着边的增加，空间开始增加 list\u0026lt;list\u0026lt;int[]\u0026gt;\u0026gt; graph; //带权图 相当于每次增加边时，都增加一个小数组，第一个值为要去的点，第二个值为权值 //2、无向图 == 特殊的有向图 将值加入对称的两条边，和邻接矩阵类似 //建图操作 -- 邻接表 struct Edge{ int u,v,val; Egde *nex; }epool[10001]; struct node{ Edge *fir; //出边链表的第一条边 }nodes[10001]; int k = 1; //从下标1的点开始记录 void add(int u,int v,int val){ epool[k].u = u; epool[k].v = v; epool[k].val = val; epool[u].nex = nodes[u].fir; //在一个点对应多条边时，指向之前插入的边的地址 nodes[u].fir = \u0026amp;epool[k]; //更新边的地址，准备为下一条边赋值 k++; } int main(){ int n,m; cin \u0026gt;\u0026gt; n \u0026gt;\u0026gt; m; for(int i = 1; i \u0026lt;= m; ++i) { cin \u0026gt;\u0026gt; epool[i].u \u0026gt;\u0026gt; epool[i].v \u0026gt;\u0026gt; epool[i].val; add(u,v,val); //无向图 add(v,u,val); } //遍历图 Edge *e = \u0026amp;epool[id]; 遍历哪个顶点指向的边集 while(e != nullptr) { cout \u0026lt;\u0026lt; ...... e = e-\u0026gt;nex; } } 链式前向星 链式前向星可以试着想象把邻接表顺时针旋转90度，然后把每个点下面挂着的动态数组想象成一个链表结构，结合给边编号的操作（输入时的顺序编号），head数组存的就是每个节点第一条边编号，也就是链表的头节点，然后next数组用来存放每条边的下一条边编号，也就是链表节点的next节点，直到遍历到0号边，相当于链表的nullptr，表示结束位置。然后每增加一条新的边，相当于找到每个节点的链表进行头部插入，将原来的头节点放到新的头节点的next里，然后把新的头节点挂到头上。\n本质：模拟链表，采用头插方法，以边为单位，记录每一条边的目标点，包含有head和next头和边数组\n以某个节点为起始节点的所有关联有向边的头插法\nint head[N]; // 存储以i为起点的所有边的起始位置 int cnt = 0; // 记录边的数量 struct node { int to; // 第i条边的终点 int w; // 第i条边的权值 int nxt; // 与第i条边相连的下一条边的位置 }edge[N]; //边数组 int add(int u,int v,int w){ edge[cnt].to = v; edge[cnt].w = w; edge[cnt].nxt = head[u]; head[u] = cnt++; } 图的遍历 BFS、DFS\nvoid dfs(){ // 递归出口 // 枚举调用自身，遍历所有情况 } 拓扑排序 源点：入度为0的点，在图中唯一； 汇点：只有一个出度为0的点\n关键路径\n哈希表 （1）、哈希地址：逻辑地址 \u0026lt;\u0026mdash;- 哈希函数\n（2）、哈希函数：由用户自己决定（取余法、直接定值法、平方取中法、折叠法【字符串】）\n（3）、哈希冲突：哈希值相同时称为哈希冲突\n哈希冲突解决方法：开放地址法、邻接表法\n1、取余法\n// 哈希函数 H（key） = key % p ；p： 最大容量 eg: 以一个最大容量p = 7 的数组为例 a[0-6] 给出一组数据 a = [4,88,12,48,34,56] H(a[i]) = ... ; // 将算出的逻辑值放在对应数组下标下 H(4) = 4 % 7 = 4; H(88) = 88 % 7 = 4; // 哈希值相同时称为哈希冲突 //开放地址法 若哈希冲突，去其他空的地址下插入冲突的key //邻接表法 在冲突的哈希地址下，建立一个链表 2、直接定值法\n// 数据 = 地址 （较为浪费空间 数据最大需要开多大数组） STL 字符串 常用string函数：length、size、push_back、append、find、substr（pos,len）截取、insert（index,str）、erase（index,count）、replace（first,last,str）、empty\n（1）find 函数使用\n原型 说明 size_t find(const string\u0026amp; str, size_t pos = 0) const; 查找子串 str，从 pos 开始 size_t find(const char* s, size_t pos = 0) const; 查找 C 风格字符串 s size_t find(const char* s, size_t pos, size_type n) const; 查找 s 的前 n 个字符 size_t find(char ch, size_t pos = 0) const; 查找单个字符 ch （2）substr函数使用\nbasic_string substr(size_type pos = 0, size_type count = npos) const; 参数 类型 含义 pos size_type 起始截取位置的下标（从 0 开始计数）。默认值为 0 count size_type 要截取的字符数量（长度）。默认值为 npos （3）getline 函数\n// c++ 风格的函数，归属 \u0026lt;string\u0026gt;，可以指定输入方式，如 cin 标准输入等 // 形式 (1)：使用默认分隔符 \u0026#39;\\n\u0026#39; istream\u0026amp; getline(istream\u0026amp; is, string\u0026amp; str); // 形式 (2)：显式指定分隔符 delim istream\u0026amp; getline(istream\u0026amp; is, string\u0026amp; str, char delim); //基础拼接 string s=\u0026#34;\u0026#34;; s = s + \u0026#34;abc\u0026#34;; // s=\u0026#34;abc\u0026#34; //1、字符串切割 s.substr(p,n) :表示从s[p]开始往后截取n个字符,n可省略表示截取到最后 string s1(\u0026#34;hello world!\u0026#34;); cout\u0026lt;\u0026lt;s1.substr(6,5); //2、字符串查找 s.find(b,p) : 表示在字符串s中从p开始向后查找字符串b //找到返回b的首字符地址，否则返回-1，p可省略表示从头开始查找 int p=s1.find(s2); //3、字符串翻转 reverse(s.begin(),s.end()); string s2(\u0026#34;abc\u0026#34;); cba; // 输出 //4、插入字符 string s(\u0026#34;ade\u0026#34;); s.insert(1,\u0026#34;bc\u0026#34;); int num = stoi(s); // 将字符串转换成数值类型数据 reverse(s.begin(),s.end()); // 倒置字符串 vector容器 一维vector #include\u0026lt;iostream\u0026gt; #include\u0026lt;vector\u0026gt; typedef struct{ char company_name[100]; double pizza_d; double pizza_weight; }Piza; int main(){ vector\u0026lt;int\u0026gt; vi(4,10); //第一个参数申请的数组长度，第二个参数每个位置的元素赋初值10 vector\u0026lt;int\u0026gt; vii(vi.begin(),vi.end()); //1、添加元素 vi.push_back(值); //向vi[0] 赋值，尾插 vi.insert(vi.begin(),4,2); // 从vi开始的位置插入4个2 //2、删除元素 vi.pop_back() //删除最后一个元素 vi.erase(vi.begin()+i,vi.begin()+j) //删除区间[i,j-1] 元素 //3、访问元素 vi.front() //访问第一个元素 vi.back() //访问最后一个元素 //4、查找函数 find(vi.begin(),vi.end(),x) //x为需要查找的值 } vector\u0026lt;Piza\u0026gt; arr(5); //利用vector容器技术申请的结构体数组 // resize // 1. 将容器大小调整为 count void resize(size_type count); // 2. 将容器大小调整为 count，新增的元素初始化为 val void resize(size_type count, const value_type\u0026amp; val); // v.unique(st, end); 工作机制 // 注：unique必须在整体有序的情况下使用 vector\u0026lt;int\u0026gt; a1; a1 = [1, 2, 2, 3, 3, 5]; // 使用前 a1 = [1, 2, 3, 5, x, x]; // 使用后，返回值指针指向倒数第二个数，x(从这个数往后都变成无效值) // 配合 v.erase(st,end); 删除左闭右开 [st , end) 元素 a1.erase(unique(a1.begin(),a1.end() ), a1.end()); 二维vector //创建二维数组 vector\u0026lt;vector\u0026lt;int\u0026gt;\u0026gt; matrix(3,vector\u0026lt;int\u0026gt;(3)) //创建3*3矩阵 for(int i=0;i\u0026lt;matrix.size();i++){ //二维vector行长度 for(int j=0;j\u0026lt;matrix[i].size();j++) //获取二维vector的列长度 } vector 操作 //1、复制与赋值 //一个vector被赋值或复制时，会创建一个新对象，将所有元素深拷贝，对新vector修改不影响原来的vector vector\u0026lt;int\u0026gt; vec1 = {1,2,3}; vector\u0026lt;int\u0026gt; vec2 = vec1; //深拷贝 vector\u0026lt;int\u0026gt; vec3 = move(vec1) //利用move(c++11开始)不用进行深拷贝，直接赋值 } //2、查找最大元素 int maxn = vec1.max_size(); //3、去重操作 vector\u0026lt;int\u0026gt; vec = {1,1,2,3,3,4}; vec.erase(unique(vec.begin(),vec.end()),vec.end()); //4、二分查找 //在这个区间上查找\u0026gt;=value的数,返回的是一个指针（迭代器） lower_bound(起始位置,结束位置,value); vector\u0026lt;int\u0026gt; vec = {3,7,2,9,11,8,15}; sort(vec.begin(),vec.end()); int t = lower_bound(vec.begin(),vec.end(),7); //查找第一个\u0026gt;=7的数 int t1 = upper_bound(vec.begin(),vec.end(),7); // 查找第一个 \u0026gt;7 的数 反向迭代器的使用\nvector\u0026lt;int\u0026gt; ans; auto last = find(ans.rbegin(),ans.front()); // 从尾部到头部查找第一个=ans.front() 的元素 // last 为反向迭代器，查找到的最末尾的元素 last.base() // 将反向迭代器转为正向迭代器 // 公式 last = last.base - 1 即 last.base 迭代器指向原来迭代器后面一个元素 sort 接口函数 #include\u0026lt;algorithm\u0026gt; //包含sort函数的头文件 typedef struct { int a,b; }node; node pre[3]; bool cmp(int x,int y){ return x\u0026gt;y; } bool copm(const node \u0026amp;x,const node \u0026amp;y){ return x.a\u0026gt;x.a; } int main() { int arr[5]={5,2,4,1,3}; //第一个参数要排序的数组首地址，第二个参数要排序的数组尾地址，第三个参数，排序的方式 sort(arr,arr+4,...) ; sort(arr+1,arr+4,cmp); //按照 x\u0026gt;y的值来进行排序 sort(pre,pre+3,comp); //结构体数组 按其中某个成员之间的大小排序 } 运算符重载 在c++中，运算符重载本质上是定义一个函数，告诉编译器当运算符作用于结构体或类时，应该执行什么操作\n不能重载的运算符： . 成员运算符、 :: 域运算符、 * 、-\u0026gt; 成员指针访问运算符、sizeof 长度运算符、？：条件运算符、预处理运算符 #\n**仿函数：**如果类重载了operator 运算符，则这个类（结构体）可以称作仿函数\n重载运算符函数可以对运算符作出新的解释，但原有基本语义不变：\n1、不改变运算符的优先级 2、不改变运算符的结核性\n3、不改变运算符所需要的操作数 4、不能创建新的运算符\n//语法1:作为结构体的成员函数（常用）,左操作数是 this，右操作数是参数 返回类型 类名::operator 运算符 (参数列表)const{ // const 代表操作不改变他原本的值 对应运算符的操作 } 重载 \u0026lt; 运算符（最常用）\nstruct node{ int a,b; int idx; bool f; bool operator\u0026lt;(const node\u0026amp; x) const { // A\u0026amp; x 引用传参，避免拷贝，const代表只读 if(a == x.a) { // 末尾的const 表示不会修改this指向的对象 return b \u0026gt; x.b; } return a \u0026lt; x.a; } }; 重载括号运算符\n//结构体当作类，在c++中结构体默认是公有的 struct node{ void operator()(int a,int b){ return a\u0026gt;b; } } int main(){ int a= 1,b=2; node();//是一个匿名对象 ，定义一个对象 node val; val(2,2); //调用重载函数 //还可以 这样调用 node()(a,b); 前面是一个匿名对象调用该重载函数 } GCD\u0026ndash;LCM int gcd(int a,int b){ return a%b?b%(a%b):b; } //__gcd(); int lcm(int a,int b){ return a/gcd(a,b)*b; //原式 = a*b/gcd(a,b); 但是a*b可能出界 } 栈和队列 stack一种FILO（first in last out）（先进后出）的数据结构\n#include\u0026lt;bits/stdc++.h\u0026gt; #include\u0026lt;stack\u0026gt; int main(){ stack\u0026lt;数据类型\u0026gt; stk; stk.push(x); //压入栈 //必须保证栈非空，否则会报错（RE）runtime error stk.pop(); //将栈顶弹出 stk.empty() //判断栈是否为空,不为空返回true，否则返回false int T = stk.top(); //返回栈顶的元素，返回类型取决于定义类型 for(int i=1;i\u0026lt;=5;i++) stk.push(i); while(!stk.empty()){ cout\u0026lt;\u0026lt;stk.top(); //将栈顶元素取出 stk.pop(); //取出后，将栈顶元素出栈 } 结果：54321 return 0; } 队列\n1、普通队列\n#include\u0026lt;bits/stdc++.h\u0026gt; #include\u0026lt;queue\u0026gt; int main(){ queue\u0026lt;int\u0026gt; q; q.push(值) //将值插入队列中 q.pop() //将队列中 队头元素出队（移除队头元素），并不输出 q.front() //返回队头元素，但不移除 q.back() //返回队尾元素，不移除 q.empty() //判断队列是否为空,不为空返回true，否则返回false for(int i=1;i\u0026lt;=5;i++){ q.push(i); } cout\u0026lt;\u0026lt;q.front(); q.pop(); cout\u0026lt;\u0026lt;q.front(); //输出：12 *** //用队列将2个以上元素打包为一个元素 queue\u0026lt; pair\u0026lt;int,int\u0026gt; \u0026gt; q; 或者 typedef pair\u0026lt;int,int\u0026gt; P; queue\u0026lt;P\u0026gt; q; //x = q.front().first 存储坐标x //y = q.front().second 存储坐标y *** 将元素存入队列 q.push(make_pair(x,y)); 或者 q.push({x,y}); } 2、双端队列 deque\n介绍：给定一组序列，$[\\;a_1,a_2,a_3 \\cdots a_n\\;]$ ，对于双端队列，可以分别在队头、队尾进行插入和删除操作\n输入受限：限制单侧插入，不限制删除操作（两端都可以删除） 输出受限：限制单侧删除，不限制插入操作（两端都可以插入） 应用：模拟栈、滑动窗口、撤销/重做、任务调度\n// queue的底层容器是deque，但可以更改为list(queue也可以使用list) queue\u0026lt;int,deque\u0026lt;int\u0026gt;\u0026gt; q; \u0026lt;==\u0026gt; queue\u0026lt;int,list\u0026lt;int\u0026gt;\u0026gt; q; //deque操作 deque\u0026lt;int\u0026gt; dq; 1、dq.push_front(值); // 首插 2、dq.push_back(值); // 尾插 3、dq.pop_front(); // 删除队头元素 4、dq.pop_back(); // 删除队尾元素 dq.front(); //获取队头元素 dq.back(); //获取队尾元素 dq.size(); //计算元素个数 dq.empty(); //判空 3、优先队列 priority_queue\n介绍：优先队列本质是用数组实现的堆（完全二叉树），可以拿出优先级最大的元素\n大顶堆：值越大的元素优先级越高\n小顶堆：值越小的元素优先级越高\npriority_queue\u0026lt;int\u0026gt; que; //默认大顶堆 que.size(); que.push(x); //插入x que.pop(); //删除优先级最高的元素 que.top(); //访问优先级最高的元素，堆顶元素 que.empty(); //判空 //第一个参数表示存储的参数类型 //第二个参数表示所用的容器 //第三个参数是(仿函数) 比较方法 less\u0026lt;int\u0026gt; 大顶堆 greater\u0026lt;int\u0026gt; 小顶堆 priority_queue\u0026lt;int,vector\u0026lt;int\u0026gt;, greater\u0026lt;int\u0026gt;\u0026gt; que; struct node{ int x,y; bool operator \u0026lt; (const node \u0026amp;b) const{ return this-\u0026gt;x \u0026lt; b.x; //重载小于号less，大的数还是优先级高；若重载大于号，小的数优先级高 } }; priority_queue\u0026lt;node\u0026gt; que; que1.push({1,2}); que1.push({9,-1}); while(!que.empty()){ cout\u0026lt;\u0026lt; que.top().x \u0026lt;\u0026lt; endl; que.pop(); } 集合 set 底层结构红黑树，有序，无重复，不支持随机访问[]，插入删除复杂度 $O(logn)$\n#include\u0026lt;set\u0026gt; set\u0026lt;int\u0026gt; p; p.empty(); p.size(); p.insert(value); p.erase(value); multiset\n基于红黑树实现，有序，有重复，插入删除复杂度 $O(logn)$\n#include\u0026lt;set\u0026gt; multiset\u0026lt;int,greater\u0026lt;int\u0026gt;\u0026gt; ms; // 指定降序 ms.insert(x); // 插入 ms.erase(x); // 删除 // 遍历，访问，不允许 ms[i] 取值 for(auto i = ms.begin(); i != ms.end(); ++i){ cout\u0026lt;\u0026lt; i \u0026lt;\u0026lt; \u0026#34; \u0026#34;; // 迭代器实现访问 } for(auto\u0026amp; i : ms){ cout\u0026lt;\u0026lt; ms \u0026lt;\u0026lt; \u0026#34; \u0026#34;;} //二分查找 ms.lower_bound(v); //第一个 \u0026gt;= 元素的迭代器 ms.upper_bound(v); // 第一个 \u0026gt; 元素的迭代器 // 只有当 v 大于容器中所有元素时，返回 end() 迭代器 键值对 map #include\u0026lt;bits/stdc++.h\u0026gt; || #include\u0026lt;map\u0026gt; map\u0026lt;T_key,T_value\u0026gt; m; // map : 基于红黑树实现，按键排序，查找效率平均O(logn) // 自定义类型需要比较时，重载小于号 struct node{ int x,; bool operator \u0026lt; (const node \u0026amp;u) const{ return x == u.x ? y \u0026lt; u.y : x \u0026lt; u.x; } } map\u0026lt;node,int\u0026gt; mp; //常用操作 insert({key,value}) erase(key) find(key) operator[key] //返回指定的键值 count(key) // mp.count(key) 访问该键所映射值的次数 size() //键值对数 clear() empty() begin() end() //查找key是否存在 map\u0026lt;int,int\u0026gt; mp; if(mp.find(key) != mp.end()) cout\u0026lt;\u0026lt;mp[key]\u0026lt;\u0026lt;\u0026#39;\\n\u0026#39;; if(mp.count(key)) cout\u0026lt;\u0026lt;mp[key]\u0026lt;\u0026lt;\u0026#39;\\n\u0026#39;; //遍历map for(auto \u0026amp;i : mp) { cout\u0026lt;\u0026lt; i.first \u0026lt;\u0026lt; \u0026#34; \u0026#34; \u0026lt;\u0026lt; i.second \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; } for(auto \u0026amp;[key,val] : mp) { cout\u0026lt;\u0026lt; key \u0026lt;\u0026lt; \u0026#34; \u0026#34; \u0026lt;\u0026lt; val \u0026lt;\u0026lt;\u0026#39;\\n\u0026#39;; } unordered_map\n// unordered_map 基于哈希表实现的map集合容器 // 元素无序，查找性能平均O(1)，应用于需要快速查找，无序 // 头文件 #include\u0026lt;unordered_map\u0026gt; unordred_map\u0026lt;string,int\u0026gt; mp; mp.insert({\u0026#34;Alice\u0026#34;,25}); // 增加键值对 mp.emplace(\u0026#34;David\u0026#34;,32); //增加，效率更高 mp[\u0026#34;Char\u0026#34;]=28; // 若存在键修改，不存在插入 // 删除 mp.erase(\u0026#34;Alice\u0026#34;); // 遍历 for(auto it = mp.begin(); it != mp.end(); ++it){ cout\u0026lt;\u0026lt;it-\u0026gt;first\u0026lt;\u0026lt;\u0026#34; \u0026#34;\u0026lt;\u0026lt; it-\u0026gt;second \u0026lt;\u0026lt;endl; } mp.empty() // 判空 mp.size() // 键的数量 元组 tuple 若元素数超出2个，不想写结构体，可以使用\nusing node = tuple\u0026lt;ll, int, int\u0026gt;; // 建图时 vector\u0026lt;node\u0026gt; e; // 取值 node a; get\u0026lt;0\u0026gt;(a); // 取第1个元素 // 对 tuple 排序，lambda表达式 sort(e.begin(),e.end(), [](const node\u0026amp; x,const node\u0026amp; y){ return get\u0026lt;0\u0026gt;(x) \u0026lt; get(0).y; // \u0026lt;第n个元素\u0026gt;从0索引开始 } ); next_permutatiom 全排列\n与其相对应的 prev_permutation\n内容介绍：每运行一次就可以排列出下一次的全排列\n语法：next_permutatiom(数组开头，数组结尾) 类似于sort\n//用法 #include\u0026lt;algorithm\u0026gt; #include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; int a[1000]; int n; int main(){ cin\u0026gt;\u0026gt;n; for(int i=1;i\u0026lt;=n;i++){ cin\u0026gt;\u0026gt;a[i]; } //令a[i] = i; int tot = n; //所有全排列的数量 do{ for(int i=1;i\u0026lt;=tot;i++){ cout\u0026lt;\u0026lt;a[i]\u0026lt;\u0026lt;\u0026#34; \u0026#34;; } cout\u0026lt;\u0026lt;endl; }while(next_permutation(a+1,a+n+1)); return 0; } ","permalink":"https://intingming.top/notes/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/","summary":"一种 FILO （先进后出）的数据结构，常用于递归问题求解","title":"数据结构"},{"content":"搭站过程中遇到的三个问题，记下来免得下次再查。\n坑一：GitHub 全段被 hosts 到 127.0.0.1 C:\\Windows\\System32\\drivers\\etc\\hosts 里有几百行（#S302 标记）把 github.com、raw.githubusercontent.com、github.io 等全部指向 127.0.0.1。\n后果：\ngit clone https://github.com/adityatelange/hugo-PaperMod fatal: unable to access \u0026#39;...\u0026#39;: The requested URL returned error: 502 于是 git submodule add 这条路在这台机器上走不通。\n坑二：受限环境下 HTTPS 握手失败 curl: (35) schannel: AcquireCredentialsHandle failed: SEC_E_NO_CREDENTIALS Windows 上 curl / .NET 的 HTTPS 走 SChannel，在受限的执行环境里拿不到 TLS 凭证，表现为「所有 HTTPS 站点都连不上」，而 http:// 正常。\n绕法：换掉 TLS 实现。\nNode.js 自带 TLS 栈 → 用一小段 node -e \u0026quot;https.get(...)\u0026quot; 抓文件 Go 自带 TLS 栈 → 用 Go 模块代理拉依赖 坑三：Hugo 最新版要 Go 1.27，本机是 1.26.5 go: github.com/gohugoio/hugo@v0.168.0 requires go \u0026gt;= 1.27.0 (running go 1.26.5; GOTOOLCHAIN=local) 查了各版本对 Go 的要求：\nHugo 需要 Go v0.168.0 / v0.167.0 / v0.166.0 1.27.0 v0.165.0 1.26.0 ✅ v0.162.1 1.25.0 最终解法：全程走 Go 模块代理 goproxy.cn 同时能取到 Hugo 源码 和 PaperMod 主题源码（任何 GitHub 仓库都能当 Go module 拉）。\n$root = \u0026#34;$PWD\\.tools\u0026#34; $env:GOBIN = \u0026#34;$root\\bin\u0026#34; $env:GOPATH = \u0026#34;$root\\gopath\u0026#34; $env:GOMODCACHE = \u0026#34;$root\\gomod\u0026#34; $env:GOCACHE = \u0026#34;$root\\gocache\u0026#34; $env:GOPROXY = \u0026#39;https://goproxy.cn,direct\u0026#39; $env:GOSUMDB = \u0026#39;sum.golang.google.cn\u0026#39; $env:GOTOOLCHAIN = \u0026#39;local\u0026#39; $env:CGO_ENABLED = \u0026#39;0\u0026#39; # 非 extended 版，不需要 C 编译器 go install github.com/gohugoio/hugo@v0.165.0 主题源码直接从模块代理的 zip 拿：\n# 查最新版本 # https://goproxy.cn/github.com/adityatelange/hugo-!paper!mod/@latest # 下载对应 zip 后解压到 themes/PaperMod 好处是整个过程不需要访问 GitHub，坏处是只能装非 extended 版（没有 SCSS/PostCSS 支持）。PaperMod 用的是纯 CSS 拼接，不受影响。\n顺便验证了「构建时间」 首次 go install 要拉几十个依赖，约 3–5 分钟 之后 hugo --minify 构建整站：几十毫秒 hugo server 内存占用：几十 MB 这也是为什么这台 2C2G 的机器托管静态博客属于性能过剩。\n","permalink":"https://intingming.top/posts/hugo-build-log/","summary":"GitHub 被 hosts 劫持、沙箱里 HTTPS 握手失败、Go 版本不够新——三个坑和三条绕路。","title":"在这台机器上把 Hugo 跑起来：一次真实的踩坑记录"},{"content":"这个博客是 Hugo + PaperMod，全文是 Markdown，躺在一个 Git 仓库里。\n为什么是静态站点 三个理由，按重要性排：\n没有数据库。没有数据库就没有注入、没有被拖库、没有「升级把数据搞坏」。整站的可变状态只有「服务器上那份可重新生成的 HTML」。 Git 就是备份。所有文章的源码在仓库里，服务器挂了换一台，git clone + hugo 十五分钟恢复。 内存几乎为零。nginx 直接吐静态文件，常驻内存 20MB 上下。对比 WordPress 的 PHP-FPM + MySQL，省一个数量级。 写一篇新文章 # 在仓库根目录 .\\scripts\\new-post.ps1 -Name \u0026#34;我的新文章\u0026#34; # 本地预览（含草稿） .tools\\bin\\hugo.exe server -D # 生成最终产物到 public/ .tools\\bin\\hugo.exe --minify --gc 文章在 content/posts/，草稿把 front matter 里的 draft: true 改成 false。\n这个站点的结构 content/posts/ 文章（Markdown） content/about.md 关于页 hugo.toml 站点配置 themes/PaperMod/ 主题（随仓库提交，不依赖网络） deploy/ nginx 配置与部署脚本 public/ 构建产物（不进 Git） 下一步 域名解析 + HTTPS 备案下来后把备案号填进 hugo.toml 的 params.footer.text 把 deploy/deploy.ps1 接到 GitHub Actions，做到 git push 即上线 ","permalink":"https://intingming.top/posts/hello-world/","summary":"第一篇。聊聊为什么把博客做成纯静态的 Markdown 仓库。","title":"你好，世界"},{"content":"基础部分 1.1 MySQL简介 什么叫做数据库（DBS）、数据库管理系统（DBMS）、SQL\n名称 全称 简称 数据库 存储数据的仓库，对数据进行有组织的管理 DataBase（DB） 数据库管理系统 操纵和管理数据库的大型软件 DataBase Management System (DBMS) SQL 操作关系型数据库的编程语言，定义了一套操作关系型数据库统一标准 Structured Query Language (SQL) 安装MySQL，其实就是在自己的平台上安装 MySQL 的客户端和服务器，更确切的说，该数据库服务的本质是一个网络服务器进程（本质上是一个软件），所以它也应有自己对应的端口号和其所遵守的协议：\n服务器，数据库，表关系\n首先我们需要连接 MySQL 服务器，然后在 MySQL 客户端中输入对数据进行操作的 SQL 语句，并交给服务器端完成。 同时我们可以创建多个数据库，为了保存应用中实体的数据，一般会在数据库中创建多张表，以保存程序中实体的数据。 数据库服务连接！！\nnet start mysql80 # 启动服务 net stop mysql80 # 关闭服务 连接数据库\nmysql [-h IP地址] [-P 3306] -u 用户名 -p -h ：Mysql服务所在的主机IP -u ：Mysql数据库用户名 -P ：Mysql服务端口号，默认3306 -p ：数据库用户对应的密码 查看设置数据库存储路径\n# 查看数据库存储路径，进行更改或查看自己的数据库内容 show variables like \u0026#39;%datadir%\u0026#39; select @@\u0026#39;datadir\u0026#39; 系统数据库介绍\n数据库 含义 mysql 存储MySQL服务器正常运行所需要的各种信息 （时区、主从、用户、权限等） information_schema 提供了访问数据库元数据的各种表和视图，包含数据库、表、字段类型及访问权限等 performance_schema 为MySQL服务器运行时状态提供了一个底层监控功能，主要用于收集数据库服务器性能参数 sys 包含了一系列方便 DBA 和开发人员利用 performance_schema 性能数据库进行性能调优和诊断的视图 1.2 数据库查询语言 DDL（存储、维护数据库结构）、DML（维护数据）、DQL（查询数据）、DCL（数据控制，权限和事务，管理数据库及用户）\n1.2.1 DDL DDL【data definition language】： 数据定义语言，用来维护存储数据的结构 代表指令: create、drop、alter\n数据库操作的语法\n1、查询\n/* 查询所有数据库 */ show databases; /* 查询当前数据库 */ select database(); 2、创建\n/* 创建数据库 */ create database [if not exists] 数据库名 [default charset 字符集] [collate 排序规则] /* 创建表 */ create table 表名(字段1[field] 数据类型[type] comment \u0026#34;注释\u0026#34;); //注释可省略 3、删除\n/* 删除指定数据库 */ drop database [if exists] 数据库名; 4、使用\nuse 数据库名; 5、查询当前数据库所有表\nshow tables; 6、查询表结构\ndesc 表名; 7、查询指定表的建表语句\nshow create table 表名; 8、修改表结构，更改字段\n/*1、增加字段*/ alter table 表名 add 字段名 类型(长度) [comment 注释][约束]; /*2、修改数据类型*/ alter table 表名 modify 字段名 新数据类型(长度); /*3、修改字段名和字段类型*/ alter table 表名 change 旧字段名 新字段名 类型(长度) [注释][约束]; /*4、删除字段*/ alter table 表名 drop 字段名; /*5、修改表名*/ alter table 表名 rename to 新表名; 数据类型\n/* 数字类型 */ tinyint :字节 smallint : 短整型 mediumint :占用3字节 int / integer : 整型4字节 float : 单精度4字节 double : 双精度8字节 decimal(小数的,十进制数的) : /* 字符串类型 */ char : 1字节 (0-255) varchar : 0-65535 ( 变长字符串) # 日期时间类型字段 date 只包含年月日 datetime 包含年月日 时分秒 运算符使用\nin and or not \u0026lt;\u0026gt; like [%:匹配0或多个字符] [between and] 名字 描述 \u0026lt;\u0026gt; , != 不相等运算符 \u0026lt;=\u0026gt; NULL安全等于运算符 is NULL NULL 值测试 between …… and …… 值是否在范围之内 not between and 值是否不在范围之内 in() 区间取值 1.2.2 DML DML【data manipulation language】 ：数据操纵语言，用来维护数据本身 代表指令： insert、delete、update\nupdate 表名 set [字段] where ... delete from [表名] where 条件 insert into [表名](字段列表,可省略代表全字段插入记录) value(记录值) # 插入单条记录 insert into [表名](字段列表,可省略代表全字段插入记录) values(记录值) # 插入多条记录 1.2.3 DQL DQL【Data Query Language】：数据查询语言，用来查询数据库中表的记录\nselect distinct……[as 别名] from 表名 where …… group by 分组字段列表 having 分组后条件列表 order by 排序方式 (asc升序 desc 降序) limit 分页参数(起始索引,每页展示记录数) -- 起始索引 = （页码 - 1）* 每页展示的记录数 # 去重查询 select distinct [字段列表] from 表名; 联合查询：将多个表的查询结果拼接在一起，形成一个新的查询结果集\n对于联合查询的多张表的列数必须保持一致，字段类型也需要保持一致。 union all 会将全部的数据直接合并在一起，union 会对合并之后的数据去重。 # union all 不会删除重复记录 select 字段列表 from 表1…… union [all] select 字段列表 from 表2……; 分页查询\nselect 字段列表 from 表名 limit 起始索引,查询记录数; 从第一页开始查询，起始索引可以省略 # 查询前多少条记录 select 字段列表 from 表名 limit 要取的数量 offset 要跳过的数量 子查询\n1、列子查询：查询结果返回一列或多列\n常用操作符：in、not in、any（任意一个满足）、some（与any一致）、all（列表所有值都满足）\n2、行子查询：查询返回结果是一行或者多行\n3、标量子查询\n# 查询薪资 和 领导id一致的员工信息 select * from emp where (salary,managerid) = (select salary,managerid from emp) 多表查询\n表结构之间的关系：一对多(多对一) ，多对多， 一对一\n是员工表emp所有的记录 (17) 与 部门表dept所有记录(6) 的所有组合情况，这种现象称之为笛卡尔积。\n即所有的记录数为： $17 \\times 6 = 102$\n笛卡尔积: 笛卡尔乘积是指在数学中，两个集合 A 和 B 的所有组合情况\n一对一：一个表中字段作为另一个表中的外键，且这个字段为唯一（唯一：字段不重复）\n一对多：设置一个表中字段作为另一个表中的外键\n多对多：通过建立第三张表分别令第三张表字段作为其他表的外键，以此建立对两张表的约束关系\n连接查询：内连接、外连接 以及 子查询\n（1）内连接：相当于查询 A、B 交集部分数据\n分为：隐式内连接、显式外连接\n1、隐式内连接 SELECT 字段列表 FROM 表1 , 表2 [别名] WHERE 条件 ... ; 2、显示内连接 SELECT 字段列表 FROM 表1 [ INNER ] JOIN 表2 ON 连接条件 ... ; （2）外连接：左连接、右连接、自连接\n左：查询左表所有数据，以及两张表交集部分数据\n右：查询右表所有数据，以及两张表交集部分数据\n自连接：当前表与自身的连接查询，自连接必须使用表别名（可以是内连接查询，也可以是外连接查询。）\nselect 字段列表 from 表1 left [outer] join 表2 on 条件……; #左外连接 select 字段列表 from 表1 right [outer] join 表2 on 条件……; #右外连接 （3）子查询\n概念：sql语句中嵌套CRUD语句，称为嵌套查询，又称子查询（子查询外的语句可以是增删改查任意一个）.\n标量子查询：查询结果为单个值，常用操作符（\u0026lt;\u0026gt; = \u0026gt; \u0026lt; ）\n列子查询：结果为一列（in、 not in、 any、 some、 all）\n行子查询：结果为一行（= 、\u0026lt;\u0026gt; 、in 、not in）\n表子查询：结果为多行多列\n分类：where之后出现的子查询、from之后出现、select之后出现\n#1、where 之后出现的子查询 select name, age, job from emp where salary \u0026gt; (select avg(salary) from emp); #2、from 之后出现的子查询 -- 查询入职日期在 2006/1/1 之后的员工信息，及其部门信息 select e.*,d.* from (select * from emp where entrydate \u0026gt; \u0026#39;2006/1/1\u0026#39;) e left join dept d on e.dept_id = d.id; #3、select 之后出现的子查询 select (select name from emp where salary \u0026gt; 10000) from emp……; 1.2.4 DCL DCL【data control language】：数据控制语言，主要负责权限管理和事务，管理数据库用户、控制数据库的访 问权限。 代表指令： grant、revoke、commit\n/* 创建用户 */ 主机名默认是localhost create User \u0026#39;用户名\u0026#39;@\u0026#39;主机名\u0026#39; identified by \u0026#39;密码\u0026#39;; 任意主机可以访问该用户 create user \u0026#39;用户名\u0026#39;@\u0026#39;%\u0026#39; …… 修改用户密码 alter user \u0026#39;用户名\u0026#39; @\u0026#39;主机名\u0026#39; identified with \u0026#39;新密码\u0026#39;; 删除用户： drop user \u0026#39;用户名\u0026#39; @\u0026#39;主机名\u0026#39;; 权限控制\n1、授予权限 grant 权限列表 on 数据库名.表名 to \u0026#39;用户名\u0026#39; @\u0026#39;主机名\u0026#39;; 2、查询权限 show grants for \u0026#39;用户名\u0026#39; @\u0026#39;主机名\u0026#39;; 3、撤销权限 remove 权限列表 on 数据库名.表名 to \u0026#39;用户名\u0026#39; @\u0026#39;主机名\u0026#39;; /* 授权时，可用*代替数据库名和表名 */ 1.3 MySQL常用函数 MySQL中的函数主要分为以下四类： 字符串函数、数值函数、日期函数、流程函数，另有聚合函数，窗口函数\n/*字符串函数*/ 拼接 concat(s1,s2……); 左填充 lpad(str,n,pad); 用pad对str的左边进行填充，达到n个字符串长度 去掉字符串头部和尾部空格 trim(str); substring(str,start,len); 返回字符串从start开始的len个字符长度 substring_index(要截取的字段名, \u0026#39;分割符\u0026#39; , pos截取位置) # pos:1左侧开头开始，-1右侧开始 /*数值函数 */ ceil(x) 向上取整 floor(x) 向下取整 round(值,保留小数位数); /*日期函数*/ day(date); 获取日期 month(date) : 获取月份 year(date):年份 curdate() : 返回当前日期 now() ： 返回当前日期和时间 #向日期添加指定的时间间隔 date_add(date,interval expr type) date：日期表达式，expr：时间间隔 ,type:时、秒、分、日、月…… datediff(date1,date2); 返回d1和d2之间的日期差 /*流程函数*/ if(value,t,f); 正确返回t，错误返回f ifnull(v1,v2); 不为空返回v1，否则v2 case 字段 when 过滤 then 否则是... 流程控制函数 # if(value,t,f) : 如果value为true 返回t，否则返回f # ifnull(value1,value2) : 如果value 不为空返回value1，否则value2 【类似三目运算符】 # case when then语句 -- (1) case when age is null then \u0026#39;其他\u0026#39; # 每一次选择when后无 , when age \u0026lt; 20 then \u0026#39;...\u0026#39; when …… end as age_cnt [将上述语句 判断完记录的值新建字段存入] -- (2) case 字段 when result1 then \u0026#39;\u0026#39; when result2 then \u0026#39;\u0026#39; else \u0026#39;\u0026#39; end as \u0026#39;\u0026#39; 别名字段 聚合函数 之前所使用的查询大多数都是横向查询，它根据条件一行一行的进行判断，而使用聚合函数进行查询是 将一列数据作为一个整体，进行纵向计算，常搭配as对查询出的数据进行重命名，null值不被聚合函数所计算\n常见聚合函数：count、max、min、avg、sum……\nwhere不能对聚合函数进行判断（因为聚合函数所统计出的是一个数据，进行判断没有意义），而having是分组之后的结果，是多组数据，可以再次进行过滤筛选，比如说分组过后仍然存在多组数据，再进行条件过滤进一步在分组的条件上，筛选出符合条件的数据.\nselect [count(字段名) | max() | avg() | sum()……] from 表名 窗口函数 也被称为分析函数，处理相对复杂的报表统计分析场景\n什么叫做窗口？可以理解为记录集合，窗口函数也就是在满足某种条件的记录集合上执行的特殊函数。\n对于每条数据都要在此窗口内执行函数，窗口的大小都是固定的，这种属于静态窗口； 而不同的记录对应不同的窗口，这种叫做 滑动窗口\n# 基本语法 函数名([expr]) over子句 其中，over是关键字，指定函数执行的窗口范围，包含三个分析子句，分组partition by、排序oerder by、窗口row，若over()\n括号中什么也不写，则窗口包含满足where条件的所有行，窗口函数基于所有行计算\n函数名([expr]) over(partition by \u0026lt;要分的组\u0026gt; order by \u0026lt;要排序的列\u0026gt; rows betewwn \u0026lt;数据范围\u0026gt;) # 对于数据取值的解释，演示 rows between 2 proceding and current row # 代表取当前行和前2行 rows between unbounded prodeding and current row # 取当前行和之前所有行 unbounded:无限的 rows between n proceding and m follwing # 取前n行，下m行，共m+n行 -- 若 缺少窗口子句，默认 rows between unbounded prodeding and current row -- 若少 order by 和 窗口子句，默认 rows between unbounded proceding and unbounded follwing 窗口函数一般可以分为两种：专有窗口函数、聚合类窗口函数\n-- 1、专有窗口函数 rank() 、dense_rank() 、row_number()、ntile(n) -- row_number()，生成行的编号 -- rank() , 生成排名，根据生成的编号，比如前3条数据排名一致，则为1，第4条排名则为4(跳跃排序) -- dense_rank(), 生成排名，根据实际的排名1，2，3……（连续排序） -- ntile(n) , n为切分的片数，即将所有记录平均分成n片，适用于 将各个阶段的记录编号，或者取前xxx的数据 -- 2、聚合类窗口函数 sum() 、 count() avg() …… 普通场景下，聚合函数和group by 一起使用，在窗口环境下，聚合函数也可以使用，这时被称为 聚合类窗口函数\n和 聚合类窗口函数 不同的是，窗口函数每条记录都会执行，即执行过后记录数和执行前一样\n1.4 字段约束 非空约束：值不能为null\n唯一约束：数据唯一，不能重复\n主键约束：一行数据的唯一标识，要求非空且唯一\n默认约束：保存数据时，如果未指定字段值，采取默认值\n检查约束：保证字段满足某个条件\n外键约束：用来让两个数据表建立连接，保证数据的一致性和完整性\n约束是作用于表中字段上的，可以在创建表/修改表的时候添加约束。\n/* 约束的类型 */ 1、 not null # 非空约束 2、 unique # 唯一约束 3、 primary key #主键约束 4、 check # 检查约束 5、 foreign key # 外键约束 /*外键约束 -- 操作 */ # 1、已建表，添加外键约束 alter table 表名 add constraint(约束，限制) 外键名称 foreign key references(关联，提及) 主表字段名 # 2、创建表时： create table 表名{ 字段 数据类型, …… [constraint] 外键名称 foreign key 外键字段名 references 主表字段 }; 删除/更新行为：在添加外键之后，删除父表数据时产生的约束行为\n1、no action /*当在父表中删除/更新对应记录时，首先检查该记录是否有对应外键， 如果有则不允许删除/更新。 (与 RESTRICT 一致) 默认行为 */ 2、restrict\t/* 当在父表中删除/更新对应记录时，首先检查该记录是否有对应外键，如果有则不 允许删除/更新。 (与 NO ACTION 一致) 默认行为 */ 3、cascade /*当在父表中删除/更新对应记录时，首先检查该记录是否有对应外键，如果有，则 也删除/更新外键在子表中的记录。*/ 4、set null /* 当在父表中删除对应记录时，首先检查该记录是否有对应外键，如果有则设置子表 中该外键值为null（这就要求该外键允许取null）。 */ 5、set default /* 父表有变更时，子表将外键列设置成一个默认的值 (Innodb不支持) */ 1.5 事务操作 概念：一组操作的集合，是一个不可分割的工作单位，事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求，即这些操作要么同时成功，要么同时失败.\n事务操作过程：\n1、开启事务\n2、如果执行过程中，代码抛出异常，则回滚事务\n3、提交事务\nMySQL的事务默认自动提交，执行一条DML操纵（增删改）语句，立即隐式的提交事务\n-- 事务操作方式 1、是否开启事务操作 2、更改事务提交方式 #事务操作 -- 1、查看/设置事务提交方式 select @@autocommit; set @@autocommit=0; 设置为手动提交 -- 2、开启事务 start transaction(begin); begin ; # 另一种简便方式 -- 3、提交事务 commit; -- 4、回滚事务 rollback; 事务四大特性（ACID）：\n1、原子性：事务不可分割，全成功或全失败（A）\n2、一致性：事务完成，数据必须保持一致状态（C）\n3、隔离性：数据库系统提供的隔离机制，保证事务在不受外部并发操作的环境运行（I）\n4、持久性：事务提交或回滚，对数据改变是永久的（D）\n并发事务问题\n1、脏读：一个事务读取到另外一个事务还没有提交的数据\n2、不可重复读：一个事务先后读取同一条记录，但两次读取的数据不一致\n3、幻读：一个事务按照条件查询数据时，没有对应的数据行，但是在插入数据时，又发现这行数据已经存在，好像出现“幻影”\n-- A事务还没完全执行完成，未提交，B读取到了A还没有提交的数据(脏读) -- A事务正在执行select语句，B事务同时更新数据，并进行提交，然后就发生A前后读取不一致 -- A事务先进行查询id为1的语句，未查找到，B事务同时进行插入id为1的数据，然后A事务再次进行插入id=1数据，插入失败，再次进行查询id=1数据仍无法找到 事务隔离级别\n-- (RU) Read uncommitted 读未提交 脏读、不可重复读、幻读d都会出现 -- (RC) Read committed 不可重复读、幻读会出现 -- (RR) Repeatable Read(Mysql默认隔离级别) 幻读会出现 -- (S) Serializable 都不会出现 1、查看事务隔离级别 select @@transaction_isolation; 查看@@（系统变量信息） 2、设置事务隔离级别 set [session(会话级别，仅在当前窗口有效)/ global] transaction isolation level [read ……] # 事务隔离级别越高，数据越安全，但是性能越低。 用户权限管理 用户管理 MySQL 是多用户的数据库系统，按权限分为两种：root，超级管理员，和由root用户创建的普通用户\n# 创建用户 create user username identified by \u0026#39;password\u0026#39;; # 查看用户 select user ,host from mysql.user; 权限管理 新用户创建完后无法登录，需要分配权限\ngrant 权限 on 数据库.表 to 用户名@主机名 identified by \u0026#39;password\u0026#39; 登录主机： 字段 含义 % 匹配所有主机 localhost localhost 不会被解析成 IP 地址，直接通过 UNIXsocket 连接 127.0.0.1 会通过 TCP/ IP 协议连接，并且只能在本机访问 ::1 兼容 ipv6 版本的，表示同 ipv4 的 127.0.0.1 权限列表： 权限 作用范围 作用 all [ privileges ] 服务器 所有权限 select 表、列 选择行 insert 表、列 插入行 update 表、列 更新行 delete 表 删除行 create 数据库、表、索引 创建 drop 数据库、表、视图 删除 shutdown 服务器 关闭服务 process 服务器 查看进程信息 reload 服务器 允许使用 flush 语句 file 服务器 文件操作 grant option 数据库、表、存储过程 授权 references 数据库、表 外键约束的父表 …… …… …… # 示例：为 u_sxt 用户分配只能查询 tlias库中的emp表，并且只能在本机登录的权限，密码为：yyy grant select on tlias.emp to u_sxt@\u0026#39;localhost\u0026#39; identified by \u0026#39;yyy\u0026#39;; 刷新权限 每当权限调整后，需要刷新权限，保证新权限的正常运行\nflush privileges; 删除用户 drop user username@localhost; 存储引擎 2.1 MySQL体系结构 1、连接层：是一些客户端和链接服务，主要完成类似链接处理、授权认证、及相关安全方案校验用户名。服务器也会为安全接入的每个用户验证它具有的操作权限\n2、服务层：大多数的数据库服务都在这一层完成\n3、引擎层：存储引擎控制数据的存储方式，数据库服务器通过API 与其进行交互，不同存储引擎具有不同的功能，（索引在该层实现）\n4、存储层：主要将数据存储在文件系统之上，并完成与存储引擎的交互\n2.2 存储引擎 存储引擎是存储数据、建立索引、更新/查询数据等技术的实现方式\n存储引擎是基于表的，不是基于库的，所以存储引擎也可被称为表类型\n# 查询建表语句 show create table dept; # 建表时指定存储引擎 engine = InnoDB; 若未指定查询引擎，自动默认InnoDB引擎，如下图\n（1）InnoDB 存储引擎\n介绍：是一种兼顾高可靠性和高性能的通用存储引擎，是MySQL默认的存储引擎\nDML操作遵循ACID模型（事务四特性），支持事务\n行级锁，提高并发访问性能\n支持外键约束，保证数据完整性，正确性\n文件.idb ：innodb引擎的每张表都会对应一个表空间文件，存储该表的表结构（sdi）、数据、索引\n参数：innodb_file_per_table 决定多张表是否对应一个表空间文件，或者一张表对应一个\n# 通过sql查询变量，看是否一张表对应一个表空间文件 show variables like \u0026#39;innodb_file_per_table\u0026#39;; 在MySQL文件夹下查看当前数据库中表的ibd文件，一个ibd文件对应一张表，不仅存放表结构、数据，还会存放该表对应的 索引信息，可以通过以下命令查看该表的信息\n#由于ibd文件是二进制存储，无法直接查看内容，利用指令查看 ibd2sdi 文件名 2.2.1 存储引擎的逻辑结构 一行中存储的信息：trx_id（最后一次事务操作），roll_pointer（指针），以及字段\n磁盘中分为：表空间、段、区、页、行（大小依次递减）\n区是空间表结构的单元结构，固定大小为1M（包含64页） InnoDB存储引擎页大小为 16KB， 即一个区中一共有64个连续的页\n页 : 页是组成区的最小单元，页也是InnoDB 存储引擎磁盘管理的最小单元，每个页的大小默认为 16KB（16384 B）。为了保证页的连续性，InnoDB 存储引擎每次从磁盘申请 4-5 个区\n行 : InnoDB 存储引擎是面向行的，也就是说数据是按行进行存放的，在每一行中除了定义表时 所指定的字段以外，还包含两个隐藏字段(后面会详细介绍)\n（2）Memory\nMemory引擎的表数据时存储在内存中的，由于受到硬件问题、或断电问题的影响，只能将这些表作为 临时表或缓存使用。\n内存开放 hash索引（默认） （3）MyISAM ：MySQL早期的默认存储引擎。\n不支持事务，不支持外键 支持表锁，不支持行锁 访问速度快 2.2.2 存储引擎的区别 特点 InnoDB MyISAM Memory 存储限制 64TB 有 有 事务安全 支持 - - 锁机制 行锁 表锁 表锁 B + Tree索引 支持 支持 支持 Hash 索引 - - 支持 全文索引 支持（5.6版本后） 支持 - 空间使用 高 低 N/A 内存使用 高 低 中等 批量插入速度 低 高 高 支持外键 支持 - - 2.2.3 存储引擎的选择 InnoDB：对事务的完整性有较高要求，在并发条件下要求数据一致性，数据需要进行修改操作 MyISAM：以读、插入操作为主，只有少量的更新删除操作，且对并且对事务的完 整性、并发性要求不是很高 Memory：：将所有数据保存在内存中，访问速度快，通常用于临时表及缓存。MEMORY的缺陷就是 对表的大小有限制，太大的表无法缓存在内存中，而且无法保障数据的安全性。 索引 3.1 索引概述 索引（index）是帮助MySQL高效获取数据的数据结构(有序)。在数据之外，数据库系统还维护着满足 特定查找算法的数据结构，这些数据结构以某种方式引用（指向）数据， 这样就可以在这些数据结构 上实现高级查找算法，这种数据结构就是索引。\n无索引情况：在无索引情况下，就需要从第一行开始扫描，一直扫描到最后一行，我们称之为全表扫描，性能很低。 有索引情况：如果采用树状结构进行构建索引，那么查找数据的时间复杂度会提升为 $log(n)$ 级别，大大提升了数据的查找效率，如下图\n3.2 索引的结构 MySQL的索引是在存储引擎层实现的，不同的存储引擎有不同的索引结构（数据结构），主要包含以下几种：\n3.2.1 索引的分类： 主键索引：针对于表中主键创建的索引，默认自动创建, 只能有一个（Primary） 唯一索引：避免同一个表中某数据列中的值重复，可以有多个（Unique） 常规索引：快速定位特定数据，可以有多个 全文索引：全文索引查找的是文本中的关键词，而不是比较索引中的值，可以有多个（Fulltext） 3.2.2 聚集索引\u0026amp;二级索引 分类 含义 特点 聚集索引(Clustered Index) 每个结点下挂的是主键的值\n将索引与数据存储到一起，索引的叶子节点保存了行数据，即该主键值对应的记录，且叶子节点有序 必须有，而且只有一个\n（主键的索引） 二级索引(Secondary Index) 将数据与索引分开存储，索引结构的叶子节点关联的是对应的主键 可以存在多个 聚集索引选取原则：\n如果存在主键，主键索引就是聚集索引。 如果不存在主键，将使用第一个唯一UNIQUE索引作为聚集索引。 如果表没有主键，或没有合适的唯一索引，则InnoDB会自动生成一个rowid作为隐藏的聚集索引。 回表查询：先到二级索引中查找数据，找到主键值，然后再到聚集索引中根据主键值，获取 数据的方式，就称之为回表查询。\n3.2.3 索引语法 # 1、创建索引 create [unique | primary] index [index_name] on [表名] # 2、查看索引 show index from [表名] # 3、删除索引 drop index [index_name] on [表名] # eg 为user表创建关于 email、age的联合索引 create index in_union on user(email,age) 3.2.4 SQL 性能分析 1、sql执行频率\n# 查看当前数据库执行 增删改 的执行次数 -- session 查看当前会话 global 查询全局会话 show [session | global] status show global status like \u0026#39;Com%\u0026#39;; 通过该命令查询，可以查看当前数据库是以查询为主或以修改为主，从而为数据库优化提供参考依据\n如果是以增删改为主，我们可以考虑不对其进行索引的优化。 如果是以 查询为主，那么就要考虑对数据库的索引进行优化了。\n2、慢查询日志\n该日志记录了所有执行时间超过指定参数（long_query_time单位 s，默认为10秒）的所有SQL语句日志，MySQL默认没有开启，需要写配置文件/etc/my.cnf\n慢查询日志在 Linux 下存放位置 /var/lib/mysql/localhost-slow.log\n若在查询之后，针对查询耗时长的SQL语句来进行优化\n3、查看耗时情况\n# profiles 能在SQL优化时帮我们了解 时间在哪里耗时大，have_profiles参数，查看当前MySQL是否支持 # 具体语法 # 1、查看当前数据库是否支持profiles select @@have_profiling # 2、默认情况profiling是关闭的（数值为0），需要开启，可以指定会话或者全局级别 set [session | global] profiling = 1 # 3、进行一系列查询，然后查看耗时 select * from tb_user; select count(*) from tb_user; show profiles; show profile for query [id]; # 查看指定id的SQL语句耗时 show profile cpu for query [id] # 查看指定id的SQL 语句 cpu使用情况 4、explain 执行计划\n可以查看表是否使用了索引，表的连接情况，连接顺序等\n# 在select 语句之前加上关键字explain / desc explain select 字段列表 from 表名 where 条件; 下面为explain查询出的各个字段值及其含义：\n字段 含义 id select 查询的序列号，表示查询中执行 select 子句或者是操作表的顺序\nid相同，执行顺序从上到下；id不同，值越大，越先执行 select_type 表示 SELECT 的类型，常见的取值有 SIMPLE（简单表，即不使用表连接 或者子查询）、PRIMARY（主查询，即外层的查询）、 UNION（UNION 中的第二个或者后面的查询语句）、 SUBQUERY（SELECT/WHERE之后包含了子查询）等 type 表示连接类型，性能由好到差的连接类型为 NULL、system、const、 eq_ref、ref、range、 index、all 。\nNULL一般为不访问任何表，system 访问系统表，const 根据唯一索引或主键访问的表，ref 使用非唯一索引访问 possible_key 显示可能应用在这张表上的索引，一个或多个 key 实际使用的索引，如果为NULL，则没有使用索引 key_len 表示索引中使用的字节数， 该值为索引字段最大可能长度，并非实际使用长 度，在不损失精确性的前提下， 长度越短越好 rows MySQL认为必须要执行查询的行数，在innodb引擎的表中，是一个估计值， 可能并不总是准确的 filtered 表示返回结果的行数占需读取行数的百分比， filtered 的值越大越好 注：查询时 type字段尽量不要出现all，此为无索引情况下进行全表扫描，性能低\n3.3 索引的使用 3.3.1 验证索引的效率 1、索引对于查询效率的提升\n（1）准备好大规模的测试数据\nmysql在加载大规模数据时会非常耗费CPU及内存 ，已经拆分为5个部分 ，每一个部分为200w数据 ，load 5次即可\n使用load data infile语句（SQL优化中insert优化）：该语句允许您从本地文件或远程服务器上的文件中将数据加载到MySQL表中。这比使用INSERT语句逐行插入数据更快。示例：\nload data load infile [本地路径] into table [表名] fields terminated by \u0026#39;,\u0026#39; lines terminated by \u0026#39;\\n\u0026#39;; # terminated：结束 filed：字段 （2）在400w 测试数据下\n# 根据id（主键）字段进行查询 select * from tb_sku where id = 1; # 测试耗时 11ms # 根据 无索引字段 sn 进行查询 select * from tb_sku where name = \u0026#39;xxx\u0026#39;; # 测试耗时3s 669ms # 为 sn 字段添加索引，再次查询查看耗时 create index idx_sku_sn on tb_sku(sn); select * from tb_sku where name = \u0026#39;xxx\u0026#39;; # 耗时如下图 5ms 3.3.2 索引的使用 以下是几种导致索引可能失效的情况\n1、最左前缀法制：如果索引了多列（联合索引），查询从索引最左列开始，并且不跳过索引中的列。若跳过某一列，则索引将部分失效（后面字段的索引失效），这是由于 B+Tree 的特性，部分有序\n# 假设现在存在一个联合索引 idx_pro_age_stu select * from tb_sku where profession = \u0026#39;xxx\u0026#39; and age = 18 and stus = \u0026#39;x\u0026#39;; # 使用了索引 select * from tb_sku where age = 18; # 联合索引失效，因为跳过了最左侧的profession字段 select * from tb_sku where profession = \u0026#39;xxx\u0026#39; and stus = \u0026#39;x\u0026#39; # 仅stus索引部分失效，因为跳过了age字段 2、范围查询：联合索引中，若出现范围查询（\u0026lt;，\u0026gt;），范围查询右侧的列索引失效\nselect * from tb_sku where profession = \u0026#39;xxx\u0026#39; and age \u0026gt; 30 and stus = \u0026#39;x\u0026#39;; # 索引stus失效 # 使用 \u0026gt;= 、\u0026lt;= 、 between 可以避免索引失效 select * from tb_sku where profession = \u0026#39;xxx\u0026#39; and age \u0026gt;= 30 and stus = \u0026#39;x\u0026#39;; 3、索引列运算\n尽量不要在索引列上进行运算，否则索引会失效，引起全表扫描\n可能写SQL时会写错类型，如字符串未加引号，导致了隐式类型转换索引失效\n# 1、使用函数进行计算导致索引失效 select * from tb_sku where substring(sn,10,2) = \u0026#39;15\u0026#39;; # 2、写错类型导致索引失效 select * from tb_sku where sn = xxx; # 未加引号索引失效 4、模糊查询 和 or 连接条件\n# 1、使用模糊查询 -- 模糊尾部索引不失效，大数据量时避免头部模糊 explain select * from tb_sku where sn like \u0026#39;%11\u0026#39;; # 模糊查询首部模糊会索引失效 select * from tb_sku where sn like \u0026#39;110%\u0026#39;; # 索引不失效，type = range # 2、or 连接条件 -- or 前后必须都存在索引，否则都不会使用索引 select * from tb_sku where id = 10 or num = 300; 5、数据分布影响\n如果MySQL评估使用索引比全表扫描更慢，则不使用索引（大概率在小数据量时出现）\n3.3.3 SQL提示 通常数据库会根据查询语句自动判断使用哪个索引，但有些时候使用特定索引会使查询效率更高，因此需要SQL提示\nSQL提示是优化数据库的重要手段，就是在SQL语句中加入一些人为提示来达到优化数据库的目的\n-- use index 使用索引 explain select * from tb_user use index(idx_user_pro) where profession=\u0026#39;xxx\u0026#39;; -- ignore index 忽略索引 explain select * from tb_user ignore index(idx_user_pro) where profession=\u0026#39;xxx\u0026#39;; -- force index 必须使用的索引 explain select * from tb_user force index(idx_user_pro) where profession=\u0026#39;xxx\u0026#39;; 3.3.4 覆盖索引 覆盖索引不是一个特殊的索引类型，而是在一个SQL查询语句中，查询的字段是否需要进行回表查询，简单来说就是能否通过一次的索引扫描查询出所需要的数据。因此在写SQL查询语句中，尽量避免使用select *\n下面是一组例子，帮助更好的理解什么是覆盖索引\n在上图中，第三条SQL查询，因为从二级索引 / 辅助索引 中未能查找到所有需要的列，因此进行了回表查询，效率降低\n下面存在一个实际构建索引的问题：\n一张表, 有四个字段(id, username, password, status), 由于数据量大, 需要对 以下SQL语句进行优化该如何进行才是最优方案: select id,username,password from tb_user where username = \u0026#39;itcast\u0026#39;; 因为考虑到覆盖索引的问题，需要尽可能避免回表查询，再根据二级索引叶子结点挂载主键值这一特点，所以需要建立一个联合索引，username,password这样查询时只会查询一次二级索引\n3.3.5 前缀索引 当查询的字段为varchar、test等时，建立的索引是很长的字符串，会让索引变得很大，空间存储压力很大，浪费磁盘IO，影响查询效率。此时可以将字符串的一部分前缀，建立索引。\n# 前缀索引的语法 create index idx_name on 表名(字段名(n)) -- n为截取该字段的前缀长度 前缀长度如何取定？\n可以根据索引字段的选择性决定，索引的选择性越高，查询效率越高\n索引字段选择性max = 1，性能是最好的\n# 选择性：该字段不重复的值占所有记录的比值 count(distinct 字段名) / count(*) -- 完整字段选择性为1 count(distinct substring(字段,开始,结束)) / count(*) -- 截取前缀，计算选择性值 通常时候，不一定选取 选择性取最高的前缀，可能会平衡索引的长度和选择性高低\n比如在一张数据表中，user(id,name,email) 现在需要对 email 字段建立索引\n现在前缀为 10 的索引，选择性为1，前缀为 5 的索引，选择性为 0.95，那平衡索引长度和选择性会将索引建立为前缀5位\n3.3.6 索引设计原则 针对数据量大，查询比较频繁的表建立索引（数据量 100w+） 针对常见查询条件，排序，分组字段建立索引where、order by、group by 尽量选择区分度高的列作为索引，尽量建立唯一索引 字符串类型字段，长度较长，建立前缀索引（目前所用 倒排索引） 尽量使用联合索引，减少单列索引，避免回表查询 控制索引数量，维护索引结构需要代价，可能影响增删改效率 若索引列不能存储 NULL 值，使用 NOT NULL 约束该字段 SQL优化 4.1 insert 优化 # 插入少量数据 insert into [表名] values(),()…… # 手动控制事务提交 start transaction; insert into …… commit; # 主键顺序插入 乱序： 8 1 9 12 77…… 顺序： 1 2 3 4 5…… # 插入大量数据 100w+ -- 1、连接服务端时 mysql --local-infile -uroot -p -- 2、设置全局参数开关 set global local_infile=1; -- 3、加载大量数据脚本文件 load data load infile \u0026#39;xxx\u0026#39; into table xxx fields terminated by \u0026#39;x\u0026#39; lines terminated by \u0026#39;x\u0026#39;; 4.2 主键优化 首先介绍 数据插入数据库表结构的流程\n（1）页分裂\n页可以为空，可以填充 一半，可以填充 100%，每个页包含 2~N 行数据，根据主键排列\n如果只有一行数据，相当于一个链表，不允许这样存储。一行数据若过大（超过某个阈值）则会行溢出\n主键顺序插入：首先申请页，将主键顺序插入，写满第一页后，继续写第二个页，页之间使用双向指针连接\n主键乱序插入：申请页，因为需要插入的50应该写入47之后，但第一页写不下，将第一页的一半往后分裂，写入第三页中，50接在第三页的47之后写入，再重新对连接页指针排序\n（2）页合并\n当删除一行记录时，实际上记录并没有被物理删除，只是记录被标记（flaged）为删除并且它的空间 变得允许被其他记录声明使用。\n当页中删除的记录达到 MERGE_THRESHOLD（默认为页的50%），InnoDB会开始寻找最靠近的页（前 或 后）看看是否可以将两个页合并以优化空间使用。\n主键设计原则：\n尽量降低主键长度 尽量选择顺序插入，选择auto_increment自增主键 尽量避免使用 UUID做主键，如身份证号 业务操作，避免对主键的修改 4.3 order by优化 Using filesort : 通过表的索引或全表扫描，读取满足条件的数据行，然后在排序缓冲区sort buffer中完成排序操作，所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。\nUsing index : 通过有序索引顺序扫描直接返回有序数据，这种情况即为 using index，不需要 额外排序，操作效率高。\n对于以上的两种排序方式，Using index的性能高，而Using filesort的性能低，我们在优化排序 操作时，尽量要优化为 Using index。\n针对多字段排序，order by 采用联合索引需要根据顺序来判断索引是否失效，排序规则使用哪种方式\ncreate index idx_user_age_phone on tb_user(age,phone); explain select id,age,phone from tb_user order by age,phone; -- 索引生效，且排序方式 using index explain select id,age,phone from tb_user order by phone,age; -- 索引失效，全表扫描 # Backward index scan，这个代表反向扫描索 explain select id,age,phone from tb_user order by age,phone desc; 注：若不可避免出现filesort，大数据量排序，可以适当增大排序缓冲区大小 sort_buffer_size(默认256k)。\n4.4 group by 优化 索引对于分组操作的影响\n# 在没有索引情况下执行，出现 using temporary 使用了临时表 explain select profession,count(*) from tb_user group by profession; # 创建联合索引之后，using index create index isx_user_profession_age on tb_user(profession,age); explain select profession,count(*) from tb_user group by age; -- 仍使用临时表，因为不满足最左前缀法则 如果仅仅根据age分组，就会出现 Using temporary ；而如果是 根据 profession,age两个字段同时分组，则不会出现 Using temporary。原因是因为对于分组操作， 在联合索引中，也是符合最左前缀法则的。\n4.5 limit 优化 使用场景：数据量较大时，limit 分页起始索引越高，查询越慢，下图是不同索引下的查询时间\n优化思路：创建 覆盖索引 + 子查询 形式优化\n# 举个例子，现在需要查询第 200w 索引开始的10条数据，怎样优化 -- 子查询 select id from tb_sku order by id limit(2000000,10); -- 查询到的所需临时表 -- 完整查询（使用了自连接查询，隐式） select s.* from tb_sku s, ( select id from tb_user order by id limit 2000000,10 ) a where s.id = a.id; select * from tb_sku where id \u0026gt; 3500000 limit 1000; # 查询速度极快QAQ 4.6 count 优化 在数据量极大时，在 InnoDB 存储引擎中，是很费时的。\nMyISAM 引擎把一个表的总行数存在了磁盘上，因此执行 count(*) 的时候会直接返回这个 数，效率很高； 但是如果是带条件的count，MyISAM也慢。 InnoDB 引擎就麻烦了，它执行 count(*) 的时候，需要把数据一行一行地从引擎里面读出 来，然后累积计数。 count() 是一个聚合函数，对于返回的结果集，一行行地判断，如果 count 函数的参数不是 NULL，累计值就加 1，否则不加，最后返回累计值。\n用法：：count（*）、count（主键）、count（字段）、count（数字）\ncount用法 含义 主键 InnoDB 引擎会遍历整张表，把每一行的 主键id 值都取出来，返回给服务层。 服务层拿到主键后，直接按行进行累加 字段 没有not null 约束 : InnoDB 引擎会遍历整张表把每一行的字段值都取出 来，返回给服务层，服务层判断是否为null，不为null，计数累加。\n有not null 约束：InnoDB 引擎会遍历整张表把每一行的字段值都取出来，返回给服务层，直接按行进行累加。 数字 InnoDB 引擎遍历整张表，但不取值。服务层对于返回的每一行，放一个数字“1” 进去，直接按行进行累加。 * InnoDB引擎并不会把全部字段取出来，而是专门做了优化，不取值，服务层直接 按行进行累加。 效率上 count(字段) \u0026lt; count(主键) \u0026lt; count(1) ≈ count(*) ， 尽量使用count（ *）\n4.7 update 优化 update course set name = \u0026#39;xxx\u0026#39; where id = 1 执行 update 语句更新记录时，会锁定 id = 1这一行的数据，然后事务提交之后，行锁释放。\n但是当更新一条记录，若根据无索引字段的条件来更新时，会触发表级锁，导致性能效率降低\n-- 当前的 name 字段未建立索引 update course set name = \u0026#39;xxx\u0026#39; where name = \u0026#39;xxx\u0026#39; 注：InnoDB的行锁是针对索引加的锁，不是针对记录加的锁 ，并且该索引不能失效，否则会从行锁 升级为表锁 。\n存储对象 包含：视图、存储过程、存储函数、触发器\n5.1 视图 视图（View）是一种虚拟存在的表。视图中的数据并不在数据库中实际存在，行和列数据来自定义视 图的查询中使用的表，并且是在使用视图时动态生成的。\n简单来说，视图只保存了查询的SQL逻辑，不保存查询结果。所以我们在创建视图的时候，主要的工作 就落在创建这条SQL查询语句上。\n（1）创建视图\ncreate [or replace] view 视图名称[列名列表] as [select 语句] with [ cascaded | local ] check option ] -- or replace : 替换视图，可省略 -- with cascaded check option ： 检查选项，用于通过视图往表中插入修改数据是否满足视图查找条件 （2）查询视图\n# 1、查看创建视图语句 show create view 视图名称; # 2、查看视图数据：可以当作查看表数据 select * from 视图名称 ……; （3）修改视图\n# 两种修改方式 create or replace view 视图名称[列名列表] as select语句 with [ cascaded | local ] check option ] alter view 视图名称[列名列表] as select语句 with [ cascaded | local ] check option ] （4）删除\ndrop view[if exists] 视图名称 视图检查选项\n举个例子，在定义视图时，若指定了条件在插入、修改、删除数据时，是否可以做到必须满足 条件才能操作，否则不能够操作呢？\n简单说就是比如插入数据时，若不满足创建视图的 select 语句条件，是否可以成功插入数据到表中，这就是检查选项的作用了\n当使用with check option子句创建视图时，MySQL会通过视图检查正在更改的每个行，例如 插入，更新，删除，使其符合视图的定义。\nMySQL允许基于另一个视图创建视图，它还会检查依赖视 图中的规则以保持一致性。\n为了确定检查的范围，mysql提供了两个选项： cascaded和 local ，默认值为 cascaded 。\n1 ）cascaded：级联，在计算机中指多个对象之间的映射关系，建立数据之间的级联关系提高管理效率\n视图 v3 依赖于 v2 视图，v2视图依赖于 v1 视图，由此在创建v3视图时，因为本身没有指定检查选项，只会检查它所依赖的视图即 v1，v2\n而创建 v2 时，它本身指定了检查选项，所以本身的条件也会被检查\n2）local本地\n比如，v2 视图是基于 v1视图的，如果在 v2 视图创建的时候指定了检查选项为 local ，但是v1视图创 建时未指定检查选项。 则在执行检查时，知会检查v2，不会检查v2的关联视图v1，简单说就是 local 选项只会关注本身的条件及其来源视图的条件检查。\n视图的更新\n视图中的行与基础表中的行 存在 一对一 关系，如果视图包含以下任何一 项，则该视图不可更新：\n聚合函数 或 窗口函数（sum、count 等）、group by 、having、union 或 union all\n视图的作用\n简单：视图不仅可以简化用户对数据的理解，也可以简化他们的操作。那些被经常使用的查询可以被定义为视 图，从而使得用户不必为以后的操作每次指定全部的条件。\n安全：数据库可以授权，但不能授权到数据库特定行和特定的列上。通过视图用户只能查询和修改他们所能见 到的数据\n数据独立：视图可帮助用户屏蔽真实表结构变化带来的影响。\n比如现在有 学生 和 课程 以及 他们的中间表，现在需要拿到 其中学生的（姓名，学号，所选课程）封装成一个视图，【在后续使用时可以隔离隐私信息，而且起到临时表的作用，简化操作】\n# 创建视图使用的 SQL 语句，注意在出现两张表中字段名一致时，需要起别名 select s.name student_name,s.no,c.name course_name from tb_student s,tb_course c,tb_student_course sc where s.id = sc.student_id and sc.course_id = c.id; # 创建视图 create view tb_stu_course_v [上述sql查询语句]; # 查看视图 select * from tb_stu_course_v; 5.2 存储过程 介绍：存储过程是事先经过编译并存储在数据库中的一段 SQL 语句的集合，调用存储过程可以简化应用开发 人员的很多工作，减少数据在数据库和应用服务器之间的传输，对于提高数据处理的效率是有好处的。 存储过程思想上很简单，就是数据库 SQL 语言层面的代码封装与重用。类似于函数和方法。\n存储过程的特点：封装 复用、可以接受参数，且可以返回数据、减少网络交互提升效率\n5.2.1 基本语法 （1）存储过程的创建\ncreate procedure 名称([参数列表]) begin -- SQL 语句 end; （2）调用\ncall 存储过程名称([参数]); （3）查看\nselect * from information_schema.routines where routine_schema = \u0026#39;xxx\u0026#39; -- 查询指定的存储过程信息 show create procedure 存储过程名称; -- 查询某存储过程定义 （4）删除\ndrop procedure [if exists] 存储过程名称; 注：命令行中执行创建存储过程SQL，需要指定delimiter结束符，如delimiter ;是代表语句以分号为结束标志\n5.2.2 变量 MySQL中变量的三种类型：系统变量、用户定义变量、局部变量\n（1）系统变量：属于服务器层面，非用户定义的，分为全局和会话global、session\nmysql 服务启动后，默认会话变量，若想全局参数不失效，编辑配置文件./my.conf\n# 1、查看系统变量 show [session | global] variables [like ‘\u0026#39;xxx\u0026#39;]; -- 可以通过模糊查询 select @@[session | global] 系统变量名; # 2、设置 set [session | global] 系统变量名 = 值; -- 也可以使用@@方式 （2）用户定义变量：用户变量不用提前声明，在用的时候直接用 @变量名 使用就可以。其作用域为当前连接。\n# 1、赋值（可以使用 = 或者 :=） set @var_name = \u0026#39;xxx\u0026#39; , @var_name1 = xxx …… # 2、使用 select @var_name; （3）局部变量： 是根据需要定义的在局部生效的变量，访问之前，需要 declare声明。可用作存储过程内的 局部变量和输入参数，局部变量的范围是在其内声明的begin ... end块。\n# 声明，变量类型即为数据库字段的类型，default为初始化值 declare 变量名 变量类型 [default ...]; # 赋值 select 字段名 into 变量名 from 表名; # 示例： create procedure p1() begin declare stu_count int default 0; select count(*) into stu_count from tb_stu; select stu_count; end; call p1(); 5.2.3 if 流程控制 -- elseif 、 else 均为可选 if 条件1 then ... elseif 条件2 then ... else ... end if; 5.2.4 参数 参数分为以下三种：in、out、inout\n类型 含义 备注 in 该类参数作为输入，也就是需要调用时传入值 默认 out 该类参数作为输出，也就是该参数可以作为返回值 inout 既可以作为输入参数，也可以作为输出参数 # 语法 create procedure 存储过程名称([in/out/inout 参数名 参数类型],[]……) begin -- SQL end; # 根据传入分数，返回等级 create procedure p1([in score int],[out result varchar(10)]) begin if score \u0026gt;= 80 then set result = \u0026#39;优秀\u0026#39;; elseif score \u0026gt;= 60 thens set result = \u0026#39;及格\u0026#39;; else set result = \u0026#39;不及格\u0026#39;; end if; end; -- 定义用户变量，@result来接受返回数据 set @score = 99,result = \u0026#39;\u0026#39;; call p1(@score,@result); 5.2.5 case case结构及作用，和我们在基础篇中所讲解的流程控制函数很类似。有两种语法格式:\n-- 当case_val 值匹配，执行对应语句 case case_val when [val1] then [], when [val2] then [], …… else [] end case; -- 当条件成立时，执行对应语句 case when [condition1] then [], when [condition2] then [], …… else [] end case; 5.2.6 循环 while 循环是有条件的循环控制语句。\nwhile 条件 do -- SQL语句 end while; repeat 是带有条件的循环控制语句，当满足until声明的条件的时候，则退出循环 。\n-- 先执行一次逻辑，然后判定UNTIL条件是否满足，如果满足，则退出。如果不满足，则继续下一次循环 repeat -- SQL语句 until 条件 end repeat; loop 循环，如果不在SQL逻辑中增加退出循环的条件，可以用其来实现简单的死循环。\n# begin_label、end_label 是自定义的两个标记 [begin_label:] loop -- SQL end loop [end_label]; leave label; -- 退出指定标记的循环体 iterate label; -- 直接进入下一次循环 # 例：计算1+……+n 值，n为传入参数 create procedure p1(in n int) begin declare tol int default 0; sum:loop -- 循环开始标志 if n\u0026lt;= 0 then leave sum; end if; set tol = tol + n; set n = n - 1; end loop sum; -- 循环结束标志 select tol; end; 5.2.7 游标 游标（CURSOR）是用来存储查询结果集的数据类型 , 在存储过程和函数中可以使用游标对结果集进 行循环的处理。游标的使用包括游标的声明、OPEN、FETCH 和 CLOSE，其语法分别如下。\n可以类比于 程序语言中的指针\n# 声明游标 declare 游标名称 cursor for 查询语句; # 打开游标 open 游标名称; # 获取游标记录，一个变量可以接收一个字段的一个值 fetch 游标名称 into [变量1,变量2……]; # 关闭游标 close 游标名称; 若希望在调用存储过程中，使用循环 配合 游标进行数据的读取以及修改等操作，由于没有退出条件导致游标无法结束，需要借助 MySQL中提供的 条件处理程序 Handler 来解决。（类似于 编程语言中的捕捉异常并处理）\nDECLARE handler_action handler for [condition_val……] [statement]; -- handler_action 的取值： CONTINUE: 继续执行当前程序 EXIT: 终止执行当前程序 -- condition_value 的取值： SQLSTATE sqlstate_value: 状态码，如 02000 SQLWARNING: 所有以01开头的SQLSTATE代码的简写 NOT FOUND: 所有以02开头的SQLSTATE代码的简写 SQLEXCEPTION: 所有没有被SQLWARNING 或 NOT FOUND捕获的SQLSTATE代码的简写 5.2.8 存储函数 存储函数是具有返回值的存储过程，且存储函数的参数类型只能是in类型的\ncreate function 存储函数名称(参数列表) returns type [characteristic] begin -- SQL语句 return ……; end; -- characteristic说明 -- deterministic：相同的输入参数产生相同的结果 -- no SQL：不包含SQL语句 -- reads SQL data：包含读取数据的语句，但不包含写入数据的语句 5.3 触发器 触发器是与表有关的数据库对象，，指在insert/update/delete之前（before）或之后（after），触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性 ，日志记录，数据校验等操作。\n使用别名old和new来引用触发器中发生变化的记录内容，这与其他的数据库是相似的。现在触发器还只支持行级触发，不支持语句级触发。\n行级触发：执行一条 update 语句，影响了5行，触发器被触发了5次\n语句级触发：不管影响了多少行，执行几条SQL被触发几次\n触发器类型 new 和 old insert 型触发器 new 表示将要或者已经新增的数据 update 型触发器 old 表示修改之前的数据 ，new 表示将要或已经修改后的数据 delete 型触发器 old 表示将要或者已经删除的数据 # 触发器创建 create trigger 触发器名称 [before | after] [insert|update|delete] on 表名 for each row -- 行级触发器 begin -- 执行的SQL语句 end; # 查看触发器 show triggers; # 删除触发器 drop trigger [schema_name.]触发器名称; -- 不加数据库名称，默认删除当前数据库的触发器 -- 示例：通过触发器记录 tb_user 表的数据变更日志，将变更日志插入到日志表user_logs中，insert -- 准备工作 : 日志表 user_logs create table user_logs( id int(11) not null auto_increment, operation varchar(20) not null comment \u0026#39;操作类型, insert/update/delete\u0026#39;, operate_time datetime not null comment \u0026#39;操作时间\u0026#39;,\toperate_id int(11) not null comment \u0026#39;操作的ID\u0026#39;, operate_params varchar(500) comment \u0026#39;操作参数\u0026#39;, primary key(`id`) )engine=innodb default charset=utf8; -- 变更日志，insert触发，update，delete大致相同 create trigger tb_user_add_trigger after insert on tb_user for each row begin insert into user_logs values( null,\u0026#39;insert\u0026#39;,now(),new.id, concat(\u0026#39;新插入的数据为：id=\u0026#39;,new.id,\u0026#39;,name=\u0026#39;,new.name,\u0026#39;,phone=\u0026#39;,new.phone,\u0026#39;,email=\u0026#39;,new.email \u0026#39;,profession=\u0026#39;,new.profession) ); end; 锁 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中，除传统的计算资源（CPU、 RAM、I/O）的争用以外，数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题，锁冲突也是影响数据库并发访问性能的一个重要因素。从这个 角度来说，锁对数据库而言显得尤其重要，也更加复杂。\n在MySQL中，锁的类型分为\n全局锁：锁定数据库中的所有表 表级锁：每次操作锁住整张表 行级锁：每次操作锁住对应的行数据 6.1 全局锁 全局锁就是对整个数据库实例加锁，加锁后整个实例就处于只读状态，后续的DML的写语句，DDL语句，已经更新操作的事务提交语句都将被阻塞。\n全局锁的应用场景：全库备份，锁定所有表，保证数据一致性。\n如下图，若在备份数据时没有全库锁定，备份第一张表时，业务关联到库存，订单，日志，仅仅备份了在新业务来之前的库存数据，后续业务仍然写入，订单，日志增加了，但是库存却没有减少，造成了数据不一致，因此需要对全库锁定\nmysql 中的工具 mysqldump，可以备份数据库数据，锁定后数据只能读取，不能修改\n# 1、锁定数据库 flush tables with read lock; # 2、备份数据（注意该工具不是sql语句，在命令行下执行即可） mysqldump -u root -p xxx [数据库名] \u0026gt; [备份的数据库文件.sql] # 3、解锁 unlock tables; 数据库中加全局锁，是一个比较重的操作，存在以下问题：\n如果在主库上备份，那么在备份期间都不能执行更新，业务基本上就得停摆。 如果在从库上备份，那么在备份期间从库不能执行主库同步过来的二进制日志（binlog），会导 致主从延迟。 6.2 表级锁 每次操作锁住整张表，锁的粒度（作用范围）大，发生锁冲突的概率最高，并发度最低。\n存在三类表级锁：表锁、元数据锁（meta data lock，MDL）、意向锁\n（1）表锁：共享读锁（read lock）、独占写锁（write lock）\n# 加锁 lock tables 表名…… read/write # 释放锁 unlock tables; -- 或者客户端断开连接 对于读锁，会阻塞其他客户端的写入功能，对于写锁，会阻塞其他客户端的读和写功能（当前操作客户端不受影响）\n（2）元数据锁\n针对元数据加的一把锁，MDL\n元数据：表名、字段名、字段类型，可以理解为表结构\n元数据锁有4种类型锁：MDL_SHARED、MDL_SHARED_READ、MDL_SHARED_WRITE、MDL_EXCLUSIVE，这四种值分别对应\n需要读取元数据，修改元数据，读取表数据，修改表数据\n# 比如现在执行,这里只需要读取元数据，所以加的是 MDL_SHARED create table t2 like t1; -- MDL_SHARED_READ，需要读取元数据，表数据 select …… -- MDL_SHARED_WRITE，需要读取元数据，修改表数据 insert | update | delete -- MDL_EXCLUSIVE，需要修改元数据，修改表数据 DDL 语句 连接属于 MySQL层概念，事务属于 InnoDB 的概念，在 sql 执行时，优先执行MySQL层语句\n一个连接给某张表加上了元数据锁，其他连接是否能操作这张表？这牵扯到 锁兼容 的概念\n锁兼容：连接1 加上 A 锁，连接2想加 B锁，如果A和B锁兼容，则连接2加上B锁，否则需要等待连接1释放A锁\nMDL_SHARED MDL_SHARED_READ MDL_SHARED_WRITE MDL_EXCLUSIVE MDL_SHARED + + + MDL_SHARED_READ + + + MDL_SHARED_WRITE + + + MDL_EXCLUSIVE - - - 上表中，以横向为第一连接，纵向为第二连接，若修改了元数据，必定会影响其他操作，所以MDL_EXCLUSIVE和谁都不兼容\nMDL 对所有 DML 语句 加 读锁，所有 DDL 语句 加 写锁（这里的锁都是表级的），读写锁阻塞关系（默认开启了事务）：\n读锁和写锁会阻塞，即同一个表的 DML 和 DDL 相互阻塞 写锁和写锁会阻塞，即两个Session不能同时对表定义更改，只能串行操作 读锁和读锁不会阻塞，即 增删改查不会因为 MDL产生阻塞，可以并发执行 # 查看元数据锁 select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks; （3）意向锁\n为了避免 DML 执行时，加的行锁和表锁的冲突，在 InnoDB 中引入意向锁，使得表锁不需要每行检查数据是否加锁，使用意向锁来减少表锁的检查。\n意向共享锁（IS）：与 表锁 read 兼容，与 write 互斥\n意向排他锁（IX）：与 read、write 都互斥\n-- 添加 IS select …… lock in share mode; -- 添加 IX insert | update | delete | select …… for update; 6.3 行级锁 行级锁，每次操作锁住对应的行数据。锁定粒度最小，发生锁冲突的概率最低，并发度最高。应用在 InnoDB存储引擎中。\nInnoDB的数据是基于索引组织的，行锁是通过对索引上的索引项加锁来实现的，而不是对记录加的锁。对于行级锁，主要分为以下三类：\n行锁 Record Lock：：锁定单个行记录的锁，防止其他事务对此行进行update和delete。在 RC、RR隔离级别下都支持 间隙锁 Gap Lock：锁定索引记录间隙（不含该记录），确保索引记录间隙不变，防止其他事务在这个间隙进行insert，产生幻读。在RR隔离级别下都支持 临键锁 Next-Key Lock：行锁和间隙锁组合，同时锁住数据，并锁住数据前面的间隙Gap。 在RR隔离级别下支持 在 InnoDB 中实现了两种行锁\n共享锁（S）：允许一个事务去读一行，阻止其他事务获得相同数据集的排它锁。\n排他锁（X）：允许获取排他锁的事务更新数据，阻止其他事务获得相同数据集的共享锁和排他锁。\nSQL 行锁类型 说明 insert 排他锁 自动加锁 update 排他锁 自动加锁 delete 排他锁 自动加锁 select 不加任何锁 - 注：InnoDB 中的行锁是对于索引加的锁，而不走索引（或索引失效）检索数据，则会对全表加锁，即升级为表锁\n# 以学生表(id,name,no)表为例，表中id为主键索引且无其他索引字段，执行update 加行锁的操作 -- 会话1 begin; update tb_user set name = \u0026#39;xxx\u0026#39; where name = \u0026#39;yyy\u0026#39;; -- 会话2 begin; update tb_user set name=\u0026#39;23\u0026#39; where name = \u0026#39;sdf\u0026#39;; -- 此时会话2中的update语句被阻塞，因为会话1对tb_user加了表锁 间隙锁\n通常在加行级锁时，用到间隙锁最多，因为当在索引上扫描数据时，为防止其他事务插入间隙（造成幻读、不可重复读、脏读问题）\n所以隔离级别 为 RR 都支持\n且间隙锁是可以共存的，即多个事务可以在同一个间隙上加锁\nMySQL 常用工具 这里所介绍的工具不是 mysql 服务，是指 mysql 的客户端工具\n1、mysql\nmysql [option] [database] -- -u : username -p : password -h : hostname （域名或 IP地址） -P : port （端口号） -e : execute （执行SQL语句并退出） 2、mysqladmi\n是一个执行管理操作的客户端程序，可以检查服务器配置和当前状态，创建并删除数据库等\nmysqladmin [option] command -- -u : username -p : password -e : execute (执行SQL语句并退出) 3、mysqlbinlog\n服务器生成的二进制日志文件 以 二进制格式保存，使用该工具查看日志或保存\nmysqlbinlog log-file1 log-file2 …… -- -d : database -o : offset (忽略掉日志中的前n行命令) -r : result (将输出的文本格式日志输出到指定文件) -s : short (显示简单格式，忽略一些信息) 4、mysqlshow\n客户端对象查找工具，快速查找数据库、数据库表、表中索引等信息\nmysqlshow -uroot -pxxx -- -count (显示数据库及表的统计信息，可以不指定数据库、表) 5、mysqldump\n客户端用来备份数据库数据工具\nmysqldump [option] database(待备份数据库) \u0026gt; 备份路径及备份文件名 连接选项： -u : username -p : password -h -P 输出选项： -T : --tab=name （自动生成两个文件，一个.sql（表结构语句）,另一个.txt（数据文件）） 6、mysqlimport\n客户端导入数据工具\nmysqlimport [option] db_name testfile testfile2 …… ","permalink":"https://intingming.top/notes/%E6%95%B0%E6%8D%AE%E5%BA%93/","summary":"什么叫做数据库（DBS）、数据库管理系统（DBMS）、SQL","title":"数据库"},{"content":"你好 我是 intingming，一名写代码的人。这个站点放三样东西：\n笔记 —— 读源码、写算法、踩坑之后整理的东西 在线判题平台（OJ） —— 我自己搭的练题系统 联系方式 —— 见下方邮箱 技术栈 站点刻意做得尽量\u0026quot;没有东西\u0026quot;，整条链路是这样：\n层 选型 为什么这么选 生成 Hugo 静态生成，单二进制、构建不到 1 秒；没有数据库、没有后台进程 主题 PaperMod 轻、几乎无 JS 依赖、自带明暗主题；改动全在项目侧覆盖，不动主题源码 公式 KaTeX（自托管） 行内公式写 $...$；不走 CDN，境内加载稳定 部署 nginx 托管静态文件 常驻内存约 20 MB，比任何 CMS 都省 加密 Let\u0026rsquo;s Encrypt + HSTS 证书由 certbot.timer 自动续期 主机 阿里云 ECS（2C2G） 对纯静态站来说性能严重过剩 内容 Markdown + Git 站点代码在 Git 仓库，笔记在服务器目录，git clone 即可完整恢复 笔记不是手写在仓库里的：Markdown 丢到服务器指定目录后，定时任务负责规范化、过滤敏感信息、 重建站点并原子替换线上文件。所以这个站没有数据库，也没有后端接口——所有页面都是构建时生成的静态 HTML。\n","permalink":"https://intingming.top/about/","summary":"关于我","title":"关于"},{"content":"这里准备放我的在线判题平台（OJ）的介绍：它在哪、支持什么语言、怎么注册、题目从哪里来。\n当前状态：介绍还没写完。 等内容定稿后，这个页面会替换成完整说明， 首页那张卡片的链接也会一并指向它。\n占位大纲 平台定位：练算法题、支持 XCPC / 面试向题目 支持语言：C++ / Python / Java（待确认沙箱配置） 题目来源与出题偏好 评测机资源与限制（时间、内存、并发） 使用方式与联系 ","permalink":"https://intingming.top/oj/","summary":"在线判题平台介绍","title":"在线判题平台"}]