From 39ce255ac93e2dff8230bb527562ac8e3b6529cd Mon Sep 17 00:00:00 2001 From: whx123 <327658337@qq.com> Date: Thu, 26 Nov 2020 00:28:29 +0800 Subject: [PATCH] =?UTF-8?q?=E2=80=98=E5=86=99=E4=BB=A3=E7=A0=81=E5=A5=BD?= =?UTF-8?q?=E4=B9=A0=E6=83=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md.bak | 0 ...22\345\275\222\350\257\246\350\247\243.md" | 380 ++++++++++++++++++ ...04\345\245\275\344\271\240\346\203\257.md" | 294 ++++++++++++++ ...\247\204\351\201\27780%\347\232\204bug.md" | 1 + 4 files changed, 675 insertions(+) delete mode 100644 README.md.bak create mode 100644 "\345\210\267leetcode\345\277\205\345\244\207\347\256\227\346\263\225/\351\200\222\345\275\222\350\257\246\350\247\243.md" create mode 100644 "\345\267\245\344\275\234\346\200\273\347\273\223/Mysql\344\270\255\357\274\21421\344\270\252\345\206\231SQL\347\232\204\345\245\275\344\271\240\346\203\257.md" create mode 100644 "\345\267\245\344\275\234\346\200\273\347\273\223/\345\206\231\344\273\243\347\240\201\346\234\211\350\277\23116\344\270\252\344\271\240\346\203\257\357\274\214\350\247\204\351\201\27780%\347\232\204bug.md" diff --git a/README.md.bak b/README.md.bak deleted file mode 100644 index e69de29..0000000 diff --git "a/\345\210\267leetcode\345\277\205\345\244\207\347\256\227\346\263\225/\351\200\222\345\275\222\350\257\246\350\247\243.md" "b/\345\210\267leetcode\345\277\205\345\244\207\347\256\227\346\263\225/\351\200\222\345\275\222\350\257\246\350\247\243.md" new file mode 100644 index 0000000..fb5ecdb --- /dev/null +++ "b/\345\210\267leetcode\345\277\205\345\244\207\347\256\227\346\263\225/\351\200\222\345\275\222\350\257\246\350\247\243.md" @@ -0,0 +1,380 @@ +### 前言 +递归是一种非常重要的算法思想,无论你是前端开发,还是后端开发,都需要掌握它。在日常工作中,统计文件夹大小,解析xml文件等等,都需要用到递归算法。它太基础太重要了,这也是为什么面试的时候,面试官经常让我们手写递归算法。本文呢,将跟大家一起学习递归算法~ + +- 什么是递归? +- 递归的特点 +- 递归与栈的关系 +- 递归应用场景 +- 递归解题思路 +- leetcode案例分析 +- 递归可能存在的问题以及解决方案 + + +**公众号:捡田螺的小男孩** + +### 什么是递归? + +递归,在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法。简单来说,递归表现为函数调用函数本身。在知乎看到一个比喻递归的例子,个人觉得非常形象,大家看一下: + +> 递归最恰当的比喻,就是查词典。我们使用的词典,本身就是递归,为了解释一个词,需要使用更多的词。当你查一个词,发现这个词的解释中某个词仍然不懂,于是你开始查这第二个词,可惜,第二个词里仍然有不懂的词,于是查第三个词,这样查下去,直到有一个词的解释是你完全能看懂的,那么递归走到了尽头,然后你开始后退,逐个明白之前查过的每一个词,最终,你明白了最开始那个词的意思。 + +来试试水,看一个递归的代码例子吧,如下: +``` +public int sum(int n) { + if (n <= 1) { + return 1; + } + return sum(n - 1) + n; +} +``` + +### 递归的特点 + +实际上,递归有两个显著的特征,终止条件和自身调用: +- 自身调用:原问题可以分解为子问题,子问题和原问题的求解方法是一致的,即都是调用自身的同一个函数。 +- 终止条件:递归必须有一个终止的条件,即不能无限循环地调用本身。 + +结合以上demo代码例子,看下递归的特点: + +![](https://imgkr2.cn-bj.ufileos.com/94f250ad-41ea-4760-ae79-060f91605aeb.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=gQGMVnPTDwM%252F5AX2OrH%252F0ugG9MA%253D&Expires=1602691176) + + +### 递归与栈的关系 +其实,递归的过程,可以理解为出入栈的过程的,这个比喻呢,只是为了方便读者朋友更好理解递归哈。以上代码例子计算sum(n=3)的出入栈图如下: +![](https://imgkr2.cn-bj.ufileos.com/d4f09883-727b-44b6-90e0-dfad9d768b8c.jpg?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=V%252BDCQxqTa24HsCxQcooMJ%252FzuOHc%253D&Expires=1602692111) + + +为了更容易理解一些,我们来看一下 函数sum(n=5)的递归执行过程,如下: +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f587c37fea484be192fec7710634ec47~tplv-k3u1fbpfcp-zoom-1.image) + +- 计算sum(5)时,先sum(5)入栈,然后原问题sum(5)拆分为子问题sum(4),再入栈,直到终止条件sum(n=1)=1,就开始出栈。 +- sum(1)出栈后,sum(2)开始出栈,接着sum(3)。 +- 最后呢,sum(1)就是后进先出,sum(5)是先进后出,因此递归过程可以理解为栈出入过程啦~ + + +### 递归的经典应用场景 +哪些问题我们可以考虑使用递归来解决呢?即递归的应用场景一般有哪些呢? +- 阶乘问题 +- 二叉树深度 +- 汉诺塔问题 +- 斐波那契数列 +- 快速排序、归并排序(分治算法也使用递归实现) +- 遍历文件,解析xml文件 + +### 递归解题思路 +解决递归问题一般就三步曲,分别是: +- 第一步,定义函数功能 +- 第二步,寻找递归终止条件 +- 第二步,递推函数的等价关系式 + +这个递归解题三板斧理解起来有点抽象,我们拿阶乘递归例子来喵喵吧~ + +#### 1.定义函数功能 +定义函数功能,就是说,你这个函数是干嘛的,做什么事情,换句话说,你要知道递归原问题是什么呀?比如你需要解决阶乘问题,定义的函数功能就是n的阶乘,如下: +``` +//n的阶乘(n为大于0的自然数) +int factorial (int n){ + +} +``` + +#### 2.寻找递归终止条件 +递归的一个典型特征就是必须有一个终止的条件,即不能无限循环地调用本身。所以,用递归思路去解决问题的时候,就需要寻找递归终止条件是什么。比如阶乘问题,当n=1的时候,不用再往下递归了,可以跳出循环啦,n=1就可以作为递归的终止条件,如下: +``` +//n的阶乘(n为大于0的自然数) +int factorial (int n){ + if(n==1){ + return 1; + } +} +``` + +#### 3.递推函数的等价关系式 +递归的**本义**,就是原问题可以拆为同类且更容易解决的子问题,即**原问题和子问题都可以用同一个函数关系表示。递推函数的等价关系式,这个步骤就等价于寻找原问题与子问题的关系,如何用一个公式把这个函数表达清楚**。阶乘的公式就可以表示为 f(n) = n * f(n-1), 因此,阶乘的递归程序代码就可以写成这样,如下: +``` +int factorial (int n){ + if(n==1){ + return 1; + } + return n * factorial(n-1); +} +``` +**注意啦**,不是所有递推函数的等价关系都像阶乘这么简单,一下子就能推导出来。需要我们多接触,多积累,多思考,多练习递归题目滴~ + +### leetcode案例分析 + +来分析一道leetcode递归的经典题目吧~ +> 原题链接在这里哈:https://leetcode-cn.com/problems/invert-binary-tree/ + +**题目:** 翻转一棵二叉树。 + +输入: +``` + 4 + / \ + 2 7 + / \ / \ +1 3 6 9 +``` +输出: +``` + 4 + / \ + 7 2 + / \ / \ +9 6 3 1 +``` + +我们按照以上递归解题的三板斧来: + +**1. 定义函数功能** + +函数功能(即这个递归原问题是),给出一颗树,然后翻转它,所以,函数可以定义为: +``` +//翻转一颗二叉树 +public TreeNode invertTree(TreeNode root) { +} + +/** + * Definition for a binary tree node. + * public class TreeNode { + * int val; + * TreeNode left; + * TreeNode right; + * TreeNode(int x) { val = x; } + * } + */ +``` + +**2.寻找递归终止条件** + +这棵树什么时候不用翻转呢?当然是当前节点为null或者当前节点为叶子节点的时候啦。因此,加上终止条件就是: +``` +//翻转一颗二叉树 +public TreeNode invertTree(TreeNode root) { + if(root==null || (root.left ==null && root.right ==null)){ + return root; + } +} +``` + +**3. 递推函数的等价关系式** + +原问题之你要翻转一颗树,是不是可以拆分为子问题,分别翻转它的左子树和右子树?子问题之翻转它的左子树,是不是又可以拆分为,翻转它左子树的左子树以及它左子树的右子树?然后一直翻转到叶子节点为止。嗯,看图理解一下咯~ +![](https://imgkr2.cn-bj.ufileos.com/938b0fcf-7ab2-4c8f-833f-382be7f8b46d.jpg?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=9BiTdqNsNnz%252FqynFWb52CMQkMnU%253D&Expires=1602692880) + + +首先,你要翻转根节点为4的树,就需要**翻转它的左子树(根节点为2)和右子树(根节点为7)**。这就是递归的**递**的过程啦 +![](https://imgkr2.cn-bj.ufileos.com/381538fe-fe34-4cae-9d1e-b5894ea542b2.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=HK51fa4jYvucnu6r3829BqFWcys%253D&Expires=1602693025) + + +然后呢,根节点为2的树,不是叶子节点,你需要继续**翻转它的左子树(根节点为1)和右子树(根节点为3)**。因为节点1和3都是**叶子节点**了,所以就返回啦。这也是递归的**递**的过程~ + +![](https://imgkr2.cn-bj.ufileos.com/c9195723-d803-4b76-9cb3-bec8192a696f.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=n5Ct31iSBnMmK2HJ7kB0I6ZhEYs%253D&Expires=1602693145) + +同理,根节点为7的树,也不是叶子节点,你需要翻转**它的左子树(根节点为6)和右子树(根节点为9)**。因为节点6和9都是叶子节点了,所以也返回啦。 + +![](https://imgkr2.cn-bj.ufileos.com/63333c60-747b-45d3-a8eb-dacf0cf36231.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=8NTb2Fj1PBlwVIxwe1InO5YqoWg%253D&Expires=1602693197) + + + +左子树(根节点为2)和右子树(根节点为7)都被翻转完后,这几个步骤就**归来**,即递归的归过程,翻转树的任务就完成了~ + +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/afada1b801734cdc899e896b0816b63f~tplv-k3u1fbpfcp-watermark.webp) + +显然,**递推关系式**就是: +``` +invertTree(root)= invertTree(root.left) + invertTree(root.right); +``` + +于是,很容易可以得出以下代码: +``` +//翻转一颗二叉树 +public TreeNode invertTree(TreeNode root) { + if(root==null || (root.left ==null && root.right ==null){ + return root; + } + //翻转左子树 + TreeNode left = invertTree(root.left); + //翻转右子树 + TreeNode right= invertTree(root.right); +} +``` +这里代码有个地方需要注意,就是翻转完一棵树的左右子树后,需要交换它左右子树的引用位置。 +``` + root.left = right; + root.right = left; +``` + +因此,leetcode这个递归经典题目的**终极解决代码**如下: +``` +class Solution { + public TreeNode invertTree(TreeNode root) { + if(root==null || (root.left ==null && root.right ==null)){ + return root; + } + //翻转左子树 + TreeNode left = invertTree(root.left); + //翻转右子树 + TreeNode right= invertTree(root.right); + //左右子树交换位置~ + root.left = right; + root.right = left; + return root; + } +} +``` +拿终极解决代码去leetcode提交一下,通过啦~ + + +![](https://imgkr2.cn-bj.ufileos.com/3c9bc7ba-1677-4b8b-a389-e7177fd2b747.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=oNJFxSVWmku%252FMg%252BKlGHop%252BQ%252BxIw%253D&Expires=1602693611) + + +### 递归存在的问题 +- 递归调用层级太多,导致栈溢出问题 +- 递归重复计算,导致效率低下 + +#### 栈溢出问题 +- 每一次函数调用在内存栈中分配空间,而每个进程的栈容量是有限的。 +- 当递归调用的层级太多时,就会超出栈的容量,从而导致调用栈溢出。 +- 其实,我们在前面小节也讨论了,递归过程类似于出栈入栈,如果递归次数过多,栈的深度就需要越深,最后栈容量真的不够咯 + +**代码例子如下:** +``` +/** + * 递归栈溢出测试 + */ +public class RecursionTest { + + public static void main(String[] args) { + sum(50000); + } + private static int sum(int n) { + if (n <= 1) { + return 1; + } + return sum(n - 1) + n; + } +} +``` +**运行结果:** +``` +Exception in thread "main" java.lang.StackOverflowError + at recursion.RecursionTest.sum(RecursionTest.java:13) +``` + +怎么解决这个栈溢出问题?首先需要**优化一下你的递归**,真的需要递归调用这么多次嘛?如果真的需要,先稍微**调大JVM的栈空间内存**,如果还是不行,那就需要弃用递归,**优化为其他方案**咯~ + +#### 重复计算,导致程序效率低下 +我们再来看一道经典的青蛙跳阶问题:一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 + +绝大多数读者朋友,很容易就想到以下递归代码去解决: +``` +class Solution { + public int numWays(int n) { + if (n == 0){ + return 1; + } + if(n <= 2){ + return n; + } + return numWays(n-1) + numWays(n-2); + } +} +``` + +但是呢,去leetcode提交一下,就有问题啦,超出时间限制了 + + +![](https://imgkr2.cn-bj.ufileos.com/47049001-7fae-4c98-8f4c-2e55aca367f3.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=xELiHeOso7vAsXwthfHEIDarVGs%253D&Expires=1602693967) + + +为什么超时了呢?递归耗时在哪里呢?先画出**递归树**看看: + +![](https://imgkr2.cn-bj.ufileos.com/9539296a-f5b1-433e-94ba-2e23eddfc409.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=Rzlo2ChPE7UEkCSJhbMNTBbtfaA%253D&Expires=1602694031) + + +- 要计算原问题 f(10),就需要先计算出子问题 f(9) 和 f(8) +- 然后要计算 f(9),又要先算出子问题 f(8) 和 f(7),以此类推。 +- 一直到 f(2) 和 f(1),递归树才终止。 + +**递归时间复杂度 = 解决一个问题时间*问题个数** +- 一个问题时间 = f(n-1)+f(n-2),也就是一个加法的操作,所以复杂度是 **O(1)**; +- 问题个数 = 递归树节点的总数,递归树的总结点 = 2^n-1,所以是复杂度**O(2^n)**。 + +因此,青蛙跳阶,递归解法的时间复杂度 = O(1) * O(2^n) = O(2^n),就是指数级别的,**如果n比较大的话,超时很正常的了**。 + +你仔细观察这颗递归树,你会发现存在**大量重复计算**,比如f(8)被计算了两次,f(7)被重复计算了3次...所以这个递归算法低效的原因,就是存在大量的重复计算! + +**那么,怎么解决这个问题呢?** + +既然存在大量重复计算,那么我们可以先把计算好的答案存下来,即造一个备忘录,等到下次需要的话,先去**备忘录**查一下,如果有,就直接取就好了,备忘录没有才再去计算,那就可以省去重新重复计算的耗时啦!这就是**带备忘录的解法** + +我们来看一下**带备忘录的递归解法**吧~ + +一般使用一个数组或者一个哈希map充当这个**备忘录**。 + +假设f(10)求解加上**备忘录**,我们再来画一下递归树: + +**第一步**,f(10)= f(9) + f(8),f(9) 和f(8)都需要计算出来,然后再加到备忘录中,如下: + +![](https://imgkr2.cn-bj.ufileos.com/22fe0dc0-136e-4e8d-9b54-7f1ff2a9d066.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=gJl54y4g86XMhK2K1ZbaqmVl94Y%253D&Expires=1602694255) + + +**第二步,** f(9) = f(8)+ f(7),f(8)= f(7)+ f(6), 因为 f(8) 已经在备忘录中啦,所以可以省掉,f(7),f(6)都需要计算出来,加到备忘录中~ + +![](https://imgkr2.cn-bj.ufileos.com/f9b26b22-c745-4bad-b14d-d8b2b51075f4.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=RkOd5Zj5Wwqonn63eXfoWqStvx4%253D&Expires=1602694275) + + +**第三步,** f(8) = f(7)+ f(6),发现f(8),f(7),f(6)全部都在备忘录上了,所以都可以剪掉。 +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ec535ab8a0d3401eae5cb041dabac221~tplv-k3u1fbpfcp-watermark.image) + +所以呢,用了备忘录递归算法,递归树变成光秃秃的咯,如下: +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5b78faafe49b4ee0ab939194459cf386~tplv-k3u1fbpfcp-watermark.image) + +带「备忘录」的递归算法,子问题个数就为n了,解决一个子问题还是O(1),所以**带「备忘录」的递归算法的时间复杂度是O(n)**。接下来呢,我们用带「备忘录」的递归算法去撸代码,解决这个青蛙跳阶问题咯~,代码如下: + +``` +public class Solution { + //哈希map充当备忘录的作用 + Map tempMap = new HashMap(); + public int numWays(int n) { + // n = 0 也算1种 + if (n == 0) { + return 1; + } + if (n <= 2) { + return n; + } + //先判断有没计算过,即看看备忘录有没有 + if (tempMap.containsKey(n)) { + //备忘录有,即计算过,直接返回 + return tempMap.get(n); + } else { + // 备忘录没有,即没有计算过,执行递归计算,并且把结果保存到备忘录map中,对1000000007取余(这个是leetcode题目规定的) + tempMap.put(n, (numWays(n - 1) + numWays(n - 2)) % 1000000007); + return tempMap.get(n); + } + } +} +``` + +去leetcode提交一下,如图,稳了: + +![](https://imgkr2.cn-bj.ufileos.com/8208e494-07e6-467b-96a2-8acf77121737.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=zNVyzVvQaWVQaQmABckQvIVh1XE%253D&Expires=1602694530) + +啥叫「自顶向下」?注意我们刚才画的递归树(或者说图),是从上向下延伸,都是从一个规模较大的原问题比如说 f(20),向下逐渐分解规模,直到 f(1) 和 f(2) 触底,然后逐层返回答案,这就叫「自顶向下」。 + + +啥叫「自底向上」?反过来,我们直接从最底下,最简单,问题规模最小的 f(1) 和 f(2) 开始往上推,直到推到我们想要的答案 f(20),这就是动态规划的思路,这也是为什么动态规划一般都脱离了递归,而是由循环迭代完成计算。 + +动态规划问题,下期分解 + +### 参考与感谢 +- [一文学会递归解题](https://mp.weixin.qq.com/s/Hew44D8rdXb3pf8mZGk67w) +- [动态规划详解](https://mp.weixin.qq.com/s/1V3aHVonWBEXlNUvK3S28w) + +### 个人公众号 +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e6b23c704fc94ca09207b779f953cce6~tplv-k3u1fbpfcp-zoom-1.image) +- 更多干货,关注公众号 + diff --git "a/\345\267\245\344\275\234\346\200\273\347\273\223/Mysql\344\270\255\357\274\21421\344\270\252\345\206\231SQL\347\232\204\345\245\275\344\271\240\346\203\257.md" "b/\345\267\245\344\275\234\346\200\273\347\273\223/Mysql\344\270\255\357\274\21421\344\270\252\345\206\231SQL\347\232\204\345\245\275\344\271\240\346\203\257.md" new file mode 100644 index 0000000..bfece93 --- /dev/null +++ "b/\345\267\245\344\275\234\346\200\273\347\273\223/Mysql\344\270\255\357\274\21421\344\270\252\345\206\231SQL\347\232\204\345\245\275\344\271\240\346\203\257.md" @@ -0,0 +1,294 @@ +### 前言 +每一个好习惯都是一笔财富,本文分SQL后悔药, SQL性能优化,SQL规范优雅三个方向,分享写SQL的21个好习惯,谢谢阅读,加油哈~ + +github地址,感谢每颗star +> https://github.com/whx123/JavaHome + +公众号:**捡田螺的小男孩** + + +### 1. 写完SQL先explain查看执行计划(SQL性能优化) + +日常开发写SQL的时候,尽量养成这个好习惯呀:写完SQL后,用explain分析一下,尤其注意走不走索引。 +``` +explain select * from user where userid =10086 or age =18; +``` + +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/758773bfbe904d5ba801bc83d81a6bbc~tplv-k3u1fbpfcp-watermark.image) + + +### 2、操作delete或者update语句,加个limit(SQL后悔药) + + +在执行删除或者更新语句,尽量加上limit,以下面的这条 SQL 为例吧: +``` +delete from euser where age > 30 limit 200; +``` + +因为加了limit 主要有这些好处: + +![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e14bfc881f21454ca40981b777f56e3e~tplv-k3u1fbpfcp-watermark.image) + + +- **降低写错SQL的代价**, 你在命令行执行这个SQL的时候,如果不加limit,执行的时候一个**不小心手抖**,可能数据全删掉了,如果**删错**了呢?加了limit 200,就不一样了。删错也只是丢失200条数据,可以通过binlog日志快速恢复的。 +- **SQL效率很可能更高**,你在SQL行中,加了limit 1,如果第一条就命中目标return, 没有limit的话,还会继续执行扫描表。 +- **避免了长事务**,delete执行时,如果age加了索引,MySQL会将所有相关的行加写锁和间隙锁,所有执行相关行会被锁住,如果删除数量大,会直接影响相关业务无法使用。 +- **数据量大的话,容易把CPU打满** ,如果你删除数据量很大时,不加 limit限制一下记录数,容易把cpu打满,导致越删越慢的。 + + +### 3. 设计表的时候,所有表和字段都添加相应的注释(SQL规范优雅) + +这个好习惯一定要养成啦,设计数据库表的时候,所有表和字段都添加相应的注释,后面更容易维护。 + +**正例:** +``` +CREATE TABLE `account` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键Id', + `name` varchar(255) DEFAULT NULL COMMENT '账户名', + `balance` int(11) DEFAULT NULL COMMENT '余额', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_name` (`name`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=1570068 DEFAULT CHARSET=utf8 ROW_FORMAT=REDUNDANT COMMENT='账户表'; +``` + +**反例:** +``` +CREATE TABLE `account` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) DEFAULT NULL, + `balance` int(11) DEFAULT NULL, + `create_time` datetime NOT NULL , + `update_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_name` (`name`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=1570068 DEFAULT CHARSET=utf8; +``` + +### 4. SQL书写格式,关键字大小保持一致,使用缩进。(SQL规范优雅) + +正例: +``` +SELECT stu.name, sum(stu.score) +FROM Student stu +WHERE stu.classNo = '1班' +GROUP BY stu.name +``` + +反例: +``` +SELECT stu.name, sum(stu.score) from Student stu WHERE stu.classNo = '1班' group by stu.name. +``` +显然,统一关键字大小写一致,使用缩进对齐,会使你的SQL看起来更优雅~ + +### 5. INSERT语句标明对应的字段名称(SQL规范优雅) + +反例: +``` +insert into Student values ('666','捡田螺的小男孩','100'); +``` +正例: +``` +insert into Student(student_id,name,score) values ('666','捡田螺的小男孩','100'); +``` + +### 6. 变更SQL操作先在测试环境执行,写明详细的操作步骤以及回滚方案,并在上生产前review。(SQL后悔药) + +- 变更SQL操作先在测试环境测试,避免有语法错误就放到生产上了。 +- 变更Sql操作需要写明详细操作步骤,尤其有依赖关系的时候,如:先修改表结构再补充对应的数据。 +- 变更Sql操作有回滚方案,并在上生产前,review对应变更SQL。 + +### 7.设计数据库表的时候,加上三个字段:主键,create_time,update_time。(SQL规范优雅) + + +反例: +``` +CREATE TABLE `account` ( + `name` varchar(255) DEFAULT NULL COMMENT '账户名', + `balance` int(11) DEFAULT NULL COMMENT '余额', +) ENGINE=InnoDB AUTO_INCREMENT=1570068 DEFAULT CHARSET=utf8 ROW_FORMAT=REDUNDANT COMMENT='账户表'; +``` +正例: +``` +CREATE TABLE `account` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键Id', + `name` varchar(255) DEFAULT NULL COMMENT '账户名', + `balance` int(11) DEFAULT NULL COMMENT '余额', + `create_time` datetime NOT NULL COMMENT '创建时间', + `update_time` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_name` (`name`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=1570068 DEFAULT CHARSET=utf8 ROW_FORMAT=REDUNDANT COMMENT='账户表'; +``` +理由: +- 主键一定要加上的,没有主键的表是没有灵魂的 +- 创建时间和更新时间的话,还是建议加上吧,详细审计、跟踪记录,都是有用的。 + +阿里开发手册也提到这个点,如图 +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8a71648ba03c4f0fa6772a3c03cd99f2~tplv-k3u1fbpfcp-watermark.image) + + +### 8. 写完SQL语句,检查where,order by,group by后面的列,多表关联的列是否已加索引,优先考虑组合索引。(SQL性能优化) + +反例: +``` +select * from user where address ='深圳' order by age ; +``` +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1320d5f64b2640ff8221251505cacf14~tplv-k3u1fbpfcp-watermark.image) + +正例: +``` +添加索引 +alter table user add index idx_address_age (address,age) +``` +![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/480d9d01a71c40228696fd22850e111d~tplv-k3u1fbpfcp-watermark.image) + +### 9.修改或删除重要数据前,要先备份,先备份,先备份(SQL后悔药) + +如果要修改或删除数据,在执行SQL前一定要先备份要修改的数据,万一误操作,还能吃口**后悔药**~ + +### 10. where后面的字段,留意其数据类型的隐式转换(SQL性能优化) + +**反例:** +``` +//userid 是varchar字符串类型 +select * from user where userid =123; +``` +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/17ad6b34e5a646028d9a086fd7ef7db0~tplv-k3u1fbpfcp-watermark.image) + +**正例:** +``` +select * from user where userid ='123'; +``` +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4bae5bd0b3564484b18e6f145ff65fd6~tplv-k3u1fbpfcp-watermark.image) + +**理由:** +- 因为不加单引号时,是字符串跟数字的比较,它们类型不匹配,MySQL会做隐式的类型转换,把它们转换为浮点数再做比较,最后导致索引失效 + + +### 11. 尽量把所有列定义为NOT NULL(SQL规范优雅) + +- **NOT NULL列更节省空间**,NULL列需要一个额外字节作为判断是否为 NULL 的标志位。 +- **NULL列需要注意空指针问题**,NULL列在计算和比较的时候,需要注意空指针问题。 + + +### 12.修改或者删除SQL,先写WHERE查一下,确认后再补充 delete 或 update(SQL后悔药) + +尤其在操作生产的数据时,遇到修改或者删除的SQL,先加个where查询一下,确认OK之后,再执行update或者delete操作 + +### 13.减少不必要的字段返回,如使用select <具体字段> 代替 select * (SQL性能优化) + +反例: +``` +select * from employee; +``` +正例: +``` +select id,name from employee; +``` +理由: + +- 节省资源、减少网络开销。 +- 可能用到覆盖索引,减少回表,提高查询效率。 + + +### 14.所有表必须使用Innodb存储引擎(SQL规范优雅) + +Innodb 支持事务,支持行级锁,更好的恢复性,高并发下性能更好,所以呢,没有特殊要求(即Innodb无法满足的功能如:列存储,存储空间数据等)的情况下,所有表必须使用Innodb存储引擎 + +### 15.数据库和表的字符集统一使用UTF8(SQL规范优雅) + +统一使用UTF8编码 +- 可以避免乱码问题 +- 可以避免,不同字符集比较转换,导致的索引失效问题 + +如果是存储表情的,可以考虑 utf8mb4 + +### 16. 尽量使用varchar代替 char。(SQL性能优化) + +反例: +``` + `deptName` char(100) DEFAULT NULL COMMENT '部门名称' +``` +正例: +``` +`deptName` varchar(100) DEFAULT NULL COMMENT '部门名称' +``` +理由: +- 因为首先变长字段存储空间小,可以节省存储空间。 + + +### 17. 如果修改字段含义或对字段表示的状态追加时,需要及时更新字段注释。 (SQL规范优雅) + +这个点,是阿里开发手册中,Mysql的规约。你的字段,尤其是表示枚举状态时,如果含义被修改了,或者状态追加时,为了后面更好维护,需要即时更新字段的注释。 + + +### 18. SQL修改数据,养成begin + commit 事务的习惯;(SQL后悔药) +正例: +``` +begin; +update account set balance =1000000 +where name ='捡田螺的小男孩'; +commit; +``` +反例: +``` +update account set balance =1000000 +where name ='捡田螺的小男孩'; +``` + + +### 19. 索引命名要规范,主键索引名为 pk_ 字段名;唯一索引名为 uk _字段名 ; 普通索引名则为 idx _字段名。(SQL规范优雅) + +说明: pk_ 即 primary key;uk _ 即 unique key;idx _ 即 index 的简称。 + + +### 20. WHERE从句中不对列进行函数转换和表达式计算 + +假设loginTime加了索引 + +**反例:** +``` +select userId,loginTime from loginuser where Date_ADD(loginTime,Interval 7 DAY) >=now(); +``` +**正例:** +``` +explain select userId,loginTime from loginuser where loginTime >= Date_ADD(NOW(),INTERVAL - 7 DAY); +``` +**理由:** +- 索引列上使用mysql的内置函数,索引失效 +![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/07bd6e743d974638a70c2db91c6cc9f2~tplv-k3u1fbpfcp-watermark.image) + + + +### 21.如果修改\更新数据过多,考虑批量进行。 + +反例: +``` +delete from account limit 100000; +``` +正例: +``` +for each(200次) +{ + delete from account limit 500; +} +``` + +理由: +- 大批量操作会会造成主从延迟。 +- 大批量操作会产生大事务,阻塞。 +- 大批量操作,数据量过大,会把cpu打满。 + +### 参考与感谢 +- [delete后加 limit是个好习惯么](https://blog.csdn.net/qq_39390545/article/details/107519747) +- 《阿里开发手册》 + +### 公众号 +后端技术栈公众号:捡田螺的小男孩 + + + + + diff --git "a/\345\267\245\344\275\234\346\200\273\347\273\223/\345\206\231\344\273\243\347\240\201\346\234\211\350\277\23116\344\270\252\344\271\240\346\203\257\357\274\214\350\247\204\351\201\27780%\347\232\204bug.md" "b/\345\267\245\344\275\234\346\200\273\347\273\223/\345\206\231\344\273\243\347\240\201\346\234\211\350\277\23116\344\270\252\344\271\240\346\203\257\357\274\214\350\247\204\351\201\27780%\347\232\204bug.md" new file mode 100644 index 0000000..dcb3d26 --- /dev/null +++ "b/\345\267\245\344\275\234\346\200\273\347\273\223/\345\206\231\344\273\243\347\240\201\346\234\211\350\277\23116\344\270\252\344\271\240\346\203\257\357\274\214\350\247\204\351\201\27780%\347\232\204bug.md" @@ -0,0 +1 @@ +### ǰ ÿһϰ߶һʲƸд16ϰߣÿܾ䣬ЩϰߣԹܶҵbugϣԴаллĶŶ~ githubַлÿstar > https://github.com/whx123/JavaHome ںţ**ݵСк** ### 1. ޸룬ǵԲһ **룬Բһ** ÿλԱرĻ䲻Ҫֽ**ֻǸһֻһô룬Բ**룬ҪԼȥһ¹Թܺܶ಻Ҫbugġ ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c730a4fe0ebb47258af9fd31f8d9c890~tplv-k3u1fbpfcp-watermark.image) ### 2. ξ УҲÿԱرĻķ**У**ǷΪգγǷԤڳȡϰ߰ɣܶ**ͼbug****У**µġ > ݿֶΪvarchar(16),Էһ32λַ㲻У**ݿֱ쳣**ˡ ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d1e3375dcfad46ca9ad88dafe7574de0~tplv-k3u1fbpfcp-watermark.image) ### 3. ޸Ͻӿڵʱ˼ӿڵļԡ ܶbugΪ޸˶Ͻӿڣȴ**ݵ**ġؼDZȽصģֱӵϵͳʧܵġֳԱ׾ͷŶ~ ԣԭӿ޸ģӿǶṩĻһҪǽӿڼݡٸӰɣdubboӿڣԭֻABһCͿԿ ``` //Ͻӿ void oldService(A,B);{ //½ӿڣnullC newService(A,B,null); } //½ӿڣʱɾϽӿڣҪݡ void newService(A,B,C); ``` ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b8d4434f90314d24bdec6742c718a5f0~tplv-k3u1fbpfcp-watermark.image) ### 4.ڸӵĴ߼ע дʱûбҪд̫ע͵ģõķõע͡ǣ**ҵ߼ܸӵĴ**ķdzбҪд**ע**עͣںά ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a5d732fc2dbb4144a84b7d566ff4861b~tplv-k3u1fbpfcp-watermark.image) ### 5. ʹIOԴҪر ӦôҶйľwindowsϵͳ**̫ļ**ϵͳͻõԺܿȻlinuxҲһƽʱļݿӣIOԴûرգôIOԴͻᱻռţ˾ûа취ˣ**Դ˷** ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e3081d16ec54487eb03a9bbd513565ec~tplv-k3u1fbpfcp-watermark.image) ʹIOʹfinallyرչ ``` FileInputStream fdIn = null; try { fdIn = new FileInputStream(new File("/jay.txt")); } catch (FileNotFoundException e) { log.error(e); } catch (IOException e) { log.error(e); }finally { try { if (fdIn != null) { fdIn.close(); } } catch (IOException e) { log.error(e); } } ``` JDK 7 ֮и˧Ĺرдʹ**try-with-resource** ``` /* * עںţݵСк */ try (FileInputStream inputStream = new FileInputStream(new File("jay.txt")) { // use resources } catch (FileNotFoundException e) { log.error(e); } catch (IOException e) { log.error(e); } ``` ### 6.ȡʩʱ߽ȣ ճУҪȡʩ**ָ߽**ʱ ƴȽϳ ``` String name = list.get(1).getName(); //listԽ磬Ϊһ2Ԫع ``` ԣӦ**ȡʩԤһ߽** ``` if(CollectionsUtil.isNotEmpty(list)&& list.size()>1){ String name = list.get(1).getName(); } ``` ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3757643c6e4044d797cc60646ce6b9d1~tplv-k3u1fbpfcp-watermark.image) ### 7.ѭԶ̵áݿȿС Զ̲ݿ**ȽϺ硢IOԴ**ģԾѭԶ̵áѭݿ⣬**һԲҪѭȥ**أҲҪһԲ̫ݹҪ500һνϣ ``` remoteBatchQuery(param); ``` ``` for(int i=0;iһӣһhttp˵ķҪconnect-timeretry ת˵Ҫĵ񣬻Ҫ**ǩǩ******ȡ֮ǰдһƪǩǩģȤѿԿһ¹ [Աرǩǩ](https://mp.weixin.qq.com/s?__biz=MzIwOTE2MzU4NA==&mid=2247484887&idx=1&sn=316cfd80f7c60b40998eab004211ebb0&chksm=977941f8a00ec8eea93bbcd7b47e7dc39c6d05117ac93f80363d171c34fd4ae64f2f5b46d0ce&token=1951383729&lang=zh_CN#rd) ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/79e0932bca0143aa971e2b6a3de2d67b~tplv-k3u1fbpfcp-watermark.image) ### 13.ӿҪݵ ӿҪݵԵģתЩҪӿڡֱ۵ҵ񳡾**ûŵ**Ľӿûholdס > - ݵȣidempotentidempotenceһѧѧڳС > - ڱ.һݵȲصִӰһִеӰͬݵȺݵȷָʹͬظִУܻͬĺ һ**ݵȼ**⼸: - ѯ - Ψһ - tokenƣֹظύ - ݿdeleteɾ - ֹ - - Rediszookeeper ֲʽǰRedisֲʽ - ״̬ݵ ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a65c2e6c0c0840a982704a0982467771~tplv-k3u1fbpfcp-watermark.image) ### 14. ߳£԰ȫ **߲**£HashMapܻѭΪǷ԰ȫģԿʹConcurrentHashMap ҲϰߣҪ־һnew HashMap(); > - HashmapArraylistLinkedListTreeMapȶԲȫģ > - VectorHashtableConcurrentHashMapȶ԰ȫ ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2477f57cad1d4b8ab49a5d4d9ddce6cb~tplv-k3u1fbpfcp-watermark.image) ### 15.ӳ⿼ Ȳ룬žȥѯ,߼Ƚϳ****ġһݿⶼ⣬ӿġдĻд⣬һǶӿ⡣ӳ٣ܿܳɹˣѯ ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/72f32d1f00b94498adf139620f2decab~tplv-k3u1fbpfcp-watermark.image) - ҪҵҪǷǿƶ⣬޸Ʒ - أЩҵ񳡾ǿԽ΢ӳһģϰ߻Ҫаɡ - дݿĴ룬Ƿӳ⡣ ### 16.ʹûʱ򣬿ǸDBһԣУ洩͸ѩͻ ͨ׵˵ʹûΪ**ÿ죬ӿںʱС**أõ棬Ҫ**ע⻺ݿһ**⡣ͬʱҪܻ洩͸ѩͻ⡣ > - ѩָݴʱ䣬ѯ޴ݿѹdown > - 洩͸ָѯһһڵݣڻDzʱҪݿѯ鲻д뻺棬⽫ڵÿҪݿȥѯݿѹ > - ָȵkeyijʱڵʱ򣬶ǡʱKeyдIJӶdb ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/30bf76e99aab4d3aae46b48580041e90~tplv-k3u1fbpfcp-watermark.image) ### ˹ں ȤѣԹעҹںŹ > ԭں **ݵСк**רעֺ̽˼㣬Javaԡ硢ݿ⡢ݽṹ㷨ϵͳܽȷ档ͨ׶~ \ No newline at end of file