以前的代码,用于收集当前方法的所有参数,放在map中方便调取
1 | import com.google.common.collect.ImmutableMap; |
附送一段代码,用于将方法中收集的参数转换成Bean
1 | import org.springframework.beans.BeanUtils; |
使用实例:
1 |
|
以前的代码,用于收集当前方法的所有参数,放在map中方便调取
1 | import com.google.common.collect.ImmutableMap; |
附送一段代码,用于将方法中收集的参数转换成Bean
1 | import org.springframework.beans.BeanUtils; |
使用实例:
1 |
|
更小的通常更好,但是要确保没有低估需要存储的值的范围,因为在schema中的多个地方增加数据类型范围是一个十分耗时的操作。
简单的数据类型的操作通常需要更少的cpu时间。
可为NULL的列使得索引、索引比统计和值比较都更为复杂。
当然也有例外,InnoDB使用单独的位存储NULL值, 所以对于稀疏数据有很好的空间效率。
一旦选定一种类型,要确保在所有的关联表中都使用同样的类型。类型之间需要精确匹配,包括像UNSIGNED这样的属性。尽量只用整型定义标识符。
其在临时表和排序时可能导致悲观的按最大长度分配内存
范式是好的,但是反范式有时也是必须的,并且能带来好处。
如果不是按照索引的最左列开始查找,则无法使用索引
不能跳过索引中的列
如果查询中有某个列的范围查询,则其右边的所有列都无法使用索引:
例如查询 索引为key(last_name, fisrt_name, dob)
1 | where last_name = 'a' and first_name like 'J%' and dob = '1877-12-23' |
如果查询中列不是独立的,则mysql不会使用索引
1 | select actor_id from sakila.actor where actor_id + 1 = 5; |
有时候需要索引很长的字符列,通常可以索引开始部分的字符,同时也会降低索引的选择性。
索引的选择性指的是,不重复的索引值和数据表的记录总数的比值。索引的选择性越高表示查询效率越高,因为选择性高的索引可以过滤掉更多的行。
前缀索引是一种能使索引更小,更快的有效方法,但是也有其缺点:前缀索引无法做order by 和 group by,也无法使用前缀索引做覆盖索引。
最容易遇到的困惑是多列索引的顺序,正确的顺序依赖于使用索引的查询,同时需要考虑如何更好的满足排序和分组需要。对于如何选择索引的列顺序有一个经验法则:将选择性最高的列放在索引的最前列。只有不需要考虑排序和分组时,将选择性跟高的列放在最前面通常是最好的,但是考虑问题需要更全面,避免随机I/O和排序更加重要。
如果一个索引包含所需要查询的字段的值,我们就可以称之为“覆盖索引”
覆盖索引的好处:
覆盖索引的陷阱:
1
select * from products where actor = 'SEAN CARREY' and title like '%APOLLO%';
mysql有两种方式可以生成有序的结果:通过排序操作;或者使用索引顺序扫描。mysql可以使用同一个索引既满足排序,有用于查找行。
只有当索引的列顺序和ORDER BY子句的顺序完全一致,并且所有列的排序方向都一样时,mysql才能使用索引来对结果做排序。如果查询需要关联多张表,则只有当ORDER BY子句引用的字段全部为第一个表时,才能用索引做排序。ORDER BY子句和查询的限制是一样的:需要满足索引的最左前缀的要求。
有一种情况下ORDER BY子句可以不满足索引的最左前缀要求:
1 | select rental_id, staff_id from sakila.rental where rental_date = '2005-05-25' |
索引为key(rental_date, inventory_id, customer_id),前导列为常量的时候,如果where子句或者join子句中对这些列指定了常量,就可以弥补ORDER BY的不足。
1 | where rental_date > '2005-12-25' order by inventory_id, customer_id; |
对于索引上是范围查询,mysql无法使用之后的索引列
索引可以让查询锁定更少的行,如果你的查询从不访问那些不需要的行,那么就会锁定更少的行。但这只有当innoDB在存储引擎层能够过滤掉所有不需要的行是才有效。如果索引无法过滤掉无效的行,那么innoDB检索到数据并返回给服务器层后,innoDB已经锁定这些行了(mysql 5.6后没有这个问题)。
下面的查询:
1 | where last_online > date_sub(now(), interval 7 day) and age bwtween 18 and 25 |
这个查询有一个问题:它有两个范围条件,last_online和age列,mysql可以使用last_online的索引或者是age列的索引,但是无法同时使用它们。
如果一个查询匹配结果有上百万行的话会怎样?
1 | select * from profiles where sex = 'm' order by rating limit 10; |
即使有索引,如果用户界面需要翻页,并且翻页到比较靠后的地方也会非常慢,如:
1 | select * from profiles where sex = 'm' order by rating limit 1000000, 10; |
无论如何创建索引,这种查询都是个严重的问题,mysql需要花费大量时间来扫描需要丢弃的数据。其中一个解决的办法是限制能够翻页的数量。
优化这类索引的另一个比较好的策略是使用延迟关联,通过使用覆盖索引返回需要的主建,再根据这些主建回主表获得需要的行,这样可以减少mysql扫描需要丢弃的行数。
1 | select * from profiles innner join |
查询性能低下最基本的原因是访问数据太多。某些查询可能不可避免的需要筛选大量数据,单这并不常见。大部分性能低下的查询都可以通过减少访问的数据量的方式进行优化。对于低效的查询,我们发现通过下面几个步骤来分析总是很有效:
第一种情况可以使用limit和选择需要的列来解决。在确定查询只返回需要的数据之后,接下来应该看看查询为了返回结果是否扫描了过多的数据,对于mysql有三个衡量查询开销的指标:
没有那个指标能完美地衡量查询的开销,但它们大致反映了mysql内部查询时需要访问多少数据,并可以大概推算出查询运行的时间。
在评估查询开销的时候,需要考虑一下从表中找到某一行数据的成本。mysql有好几种访问方式可以查找并返回一行结果。有些访问方式可能需要扫描很多行才能返回一行结果,也有些访问方式可能无需扫描就能返回结果。
在explain语句中的type列反应了访问类型。访问类型有很多种,从全表扫描到索引扫描、范围扫描、唯一索引查询、常数引用等。这里列的这些,速度是从慢到快,扫描行数也是从大到小。如果查询没有办法找到合适是访问类型,那么解决的最好办法通常是增加一个合适的索引。
一般mysql能够使用如下三种方式应用where条件,从好到坏依次为:
如果发现查询需要扫描大量的数据但只返回少数的行,那么可以使用下面的技巧去优化它:
有时候对于一个大查询我们需要分而治之,将大查询分成小查询。删除旧数据是一个很好的例子,如果用一个大的语句一次性完成,则可能一次需要锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞恨到重要的查询。
例如下面这个查询:
1 | select * from tag |
可以分解成下面这个查询来代替:
1 | select * from tag where tag = 'mysql'; |
用分解关联查询的方式重构查询有如下的优势:
因为字符比较是从右往左比较的,所以第一层循环 needle.length + 1
<= i < haystack.length
。
1 | start=needle.length - 1 end=haystack.length - 1 |
第二层循环中i变量表示坏字符的位置、j表示搜索坏字符开始位置
1 | i |
i,j指针向左搜索,如果完全匹配直接返回i即可
1 | for (j = needle.length - 1; needle[j] == haystack[i]; --i, --j) { |
坏字符:从右向左第一个不匹配的字符
好字符:从坏字符下一个字符直到最后的字符
可以认为字符移动有两种策略:坏字符对齐和好字符对齐,然后选择字符移动距离大的策略即可
1 | /** |
[片段] Mybatis ParameterHandler实践
用来批量加密用@Decrypted注解的String字段,可能还有一些坑。
1 |
|
这回是标题党, 记录下两个分而治之的排序算法(手写),分而治之的算法很容易改造成并行算法,肯定是未来的潮流, leetcode已通过, 两个算法都使用了原地(inplace)更新。
1 | class Solution { |
Manacher 算法 容易理解,实现起来也没什么大坑,复杂度还是 O(n)的, 花半个小时实现下很有意思
1 | class Solution { |
最简单的解决方法,实现简单。但是在微服务中调用接口次数太多,性能很差。
实现较复杂,但是性能好很多,下面主要介绍这种方法的思路
以分页读取数据为例:
为什么需要所属权限这个字段呢? 因为决定能否看到这行是有你所拥有的所有权限决定的,而决定能否看到哪个列是由这行所拥有的权限决定的。
如何获取该行所拥有的权限呢,我的做法是分不同的权限查询结果通过union 组合起来
记录原始顺序是因为后面分组后会打乱, 为什么要分组?分组后同样的查询才能聚合在一起,可以简化代码
这里我使用的graphql来选择需要查询的字段
可以使用guava Ordering工具类方便生成Compartor
[]: https://blog.yamato.moe/2018/11/06/2018-11-06-biz/ “根据权限查询时避免角色切换的一种思路”
[]: https://blog.yamato.moe/2019/04/04/Mybatis%20ResultSetHandler_2019-04-04%20%E7%BB%AD/ “【片段】 Mybatis ResultSetHandler 实践-续”
[]: https://blog.yamato.moe/2019/01/09/Mybatis%20ResultSetHandler_2019-01-09/ “【片段】 Mybatis ResultSetHandler 实践”
104. Maximum Depth of Binary Tree (Easy)
递归计算二叉树左右两边深度,取最大值。
1 |
|
110. Balanced Binary Tree (Easy)
递归遍历二叉树左右子树深度
1 | /** |
543. Diameter of Binary Tree (Easy)
递归遍历二叉树左右子树深度, 路径就是两边子树深度之和
1 | /** |
226. Invert Binary Tree (Easy)
递归交换左右子树的引用
1 | /** |
617. Merge Two Binary Trees (Easy)
递归时如果其中一个节点是空,可以直接复用该节点。如果新建节点,需要拷贝节点的左右子树引用,递归时会用到。
1 | /** |
Leetcode : 112. Path Sum (Easy)
递归查询子树和是否等于目标和
1 | /** |
双层递归
以root为根节点的路径数量= 以root为起点统计路径和+root左节点为起点统计路径和+root右节点为起点统计路径和
1 | /** |
572. Subtree of Another Tree (Easy)
1 | /** |
1 | /** |
111. Minimum Depth of Binary Tree (Easy)
和最大路径类似
1 | /** |
404. Sum of Left Leaves (Easy)
1 | /** |
337. House Robber III (Medium)
递归查询两种情况
1 | /** |
Second Minimum Node In a Binary Tree (Easy)
第二小节点在子树节点上,如果子树值与根节点相等,继续向下查找
1 | /** |
637. Average of Levels in Binary Tree (Easy)
BFS
1 | /** |
513. Find Bottom Left Tree Value (Easy)
DFS
1 | /** |
入栈条件: 未访问过该节点
出栈条件: 访问过该节点
1 | /** |
入栈条件: 无
出栈条件: 直接出栈
1 | /** |
入栈条件: 未访问过该节点
出栈条件: 访问过该节点
入栈顺序: right -> middle -> left
1 | /** |
1 | /** |
1 | /** |
1 | /** |
1 | /** |
1 | /** |
二叉树中序遍历
1 | /** |
链表转数组
1 | /** |
还可以使用双指针找到链表中间节点,缺点是重复遍历节点
1 | /** |
自己写两次遍历搜索二叉树,注意要排除自身节点
1 | /** |
正经思路, 中序遍历转化为排序数组, 使用双指针查找
1 | /** |
使用多个角色查询列表时,会遇到两个维度的不同点:
举一个例子:
假设存在一个登录员工拥有两个角色:
那么,在列表中他能看见:
基本信息 | 薪酬信息 | 长期激励信息 | |
---|---|---|---|
低职级/无长期激励 | √ | √ | x |
低职级/长期激励 | √ | √ | √ |
高职级/无长期激励 | x | x | x |
高职级/长期激励 | √ | x | √ |
基本思路已经在前期概要里介绍,本人已经实践了一段时间,挖了两个深坑正在解决中。
最开始的实现中数据是一条一条读取的,同时薪酬字段属于加密信息,使用了第三方微服务提供解密,读取字段多+解密字段多 导致了在百条分页的情况下接口在超时的边缘不断试探。。。
解决方案:
因为之前为了方便我们解密使用了mybatis的TypeHandler做到字段隐式加解密,目前我们的做法是对于单条数据的加解密,还是保持原来的typeHandler做法,而对批量数据处理,重新写一套数据实体,同时使用mybatis的拦截器对查询的批量数据做批量解密的处理。具体做法可以参见我的另一片文章:【片段】 Mybatis ResultSetHandler 实践-续
批量查询返回的列表中列字段都是一致的,而我们的需求是不同的行能看见不同的列字段,把批量查询出来的列表直接返回是有问题的,这个问题因为疏忽导致了线上的一次故障。
所以目前的思路是先做一次数据批量预取,之后在对列字段做处理,隐藏掉不能看见的字段。
没有想到当时想解决权限查询时避免角色切换这个问题时会遇到这么多困难,想法是正确的,在实际执行时还是困难重重。值得欣慰的在最开始的时候思路和方向都是正确的,同时也把其中遇到的各种问题和心得记录了下来,经过层层积累,才到达现在的高度。
[]: https://blog.yamato.moe/2018/11/06/2018-11-06-biz/ “根据权限查询时避免角色切换的一种思路”
[]: https://blog.yamato.moe/2019/04/04/Mybatis%20ResultSetHandler_2019-04-04%20%E7%BB%AD/ “【片段】 Mybatis ResultSetHandler 实践-续”
[]: https://blog.yamato.moe/2019/01/09/Mybatis%20ResultSetHandler_2019-01-09/ “【片段】 Mybatis ResultSetHandler 实践”