如何drop掉mysql库中的1TB表单

thbcm阅读(195)

如果问你怎么从MySQL数据库空drop一张表,很多人估计会说,很简单啊,用drop table t_test语句不就行了。这可真是初生牛犊不怕虎,你要真觉得这么简单的话,去线上业务库中drop掉一张1TB大小的表,造成长时间的业务无法访问数据库,更严重,导致数据库崩溃,宕机都是可能的。

下面就先聊聊,drop table语句背后的事情,语句执行之后,主要做2两件事情

1、清除Buffer Pool缓冲

drop table时,innodb引擎会清理该表在每个buffer pool实例中中对应的数据块页面,为了避免对系统的影响,这里的清除操作并不是真正的flush,而是将涉及到的页面从flush队列中摘除。但在摘除过程中,删除进程会持有每个buffer pool的全局锁,然后搜索这个buffer pool里对应的页面以便从flush list中删除。如果在buffer pool中需要被搜索并删除的页面过多,那么遍历时间就会增大,这就导致了其他事务操作被阻塞,严重时可导致数据库锁住。

(推荐课程:MySQL教程)

在这里还需要注意一件事情,如果数据库的buffer pool设置的很大,就会导致遍历时间变长 清理buffer pool时,还包含清理AHI包含此表的数据,AHI的功能在这里就不多说了,主要是当b+tree的层级变高时,为避免b+tree逐层搜索,AHI能根据某个检索条件,直接查询到对应的数据页,跳过逐层定位的步骤。其次AHI会占用 1/16 的buffer pool的大小,如果线上表数据不是特别大,不是超高并发,不建议将开启AHI,可以考虑关闭AHI功能

mysql> SHOW GLOBAL VARIABLES LIKE 'innodb_adaptive_hash_index';
+----------------------------+-------+
| Variable_name                   | Value |
+----------------------------+-------+
| innodb_adaptive_hash_index | ON    |
+----------------------------+-------+
1 row in set (0.01 sec)


mysql> SET GLOBAL innodb_adaptive_hash_index=OFF;
Query OK, 0 rows affected (0.00 sec)


mysql> SHOW GLOBAL VARIABLES LIKE 'innodb_adaptive_hash_index';
+----------------------------+-------+
| Variable_name                   | Value |
+----------------------------+-------+
| innodb_adaptive_hash_index | OFF   |
+----------------------------+-------+
1 row in set (0.01 sec)

2、删除对应的磁盘数据文件ibd

在删除数据文件时,如果数据文件过大,删除过程会产生大量的IO并耗费更多的时间,造成磁盘IO开销飙升,CPU负载过高,影响其他程序运行。我的一个好伙伴,就曾在线上库删除了一张 1TB 大小的表,结果20分钟,数据库无响应,最后库崩溃,重启了。

既然知道drop table做了2件事情,那就针对以上 2 个事情进行优化

在清除Buffer Pool缓冲上,为减少当个buffer pool的大小,可以合理设置innodb_buffer_pool_instances参数,减少buffer pool数据块列表扫描时间,同时关闭AHI功能

在步骤2上,可以巧妙的利用linux的硬连接特性,延迟删除真正的物理文件。

首先看看linux系统的硬链接示意图

当多个文件名同时指向同一个INODE时,这个INODE的引用数 N>1, 删除其中任何一个文件名都会很快.因为其直接的物理文件块没有被删除.只是删除了一个指针而已;当INODE的引用数 N=1 时, 删除文件需要去把这个文件相关的所有数据块清除,所以会比较耗时;

如果给数据库表的.ibd文件创建一个硬链接,当删除表时,删除物理文件时,其实删除的就是物理文件的一个指针,所以删除操作响应速度会非常快,大约不到1秒左右

下面就来演示一下具体的操作

先创建表文件的硬链接
ln t_test.ibd t_test.ibd.bak
删除表
drop table t_test;

最后就是要真正删除掉物理文件,释放文件所占用的磁盘空间,那么问题来了,如果优雅的删除物理文件呢,在这里推荐大家coreutils工具集中的truncate命令

当然需要你先安装相关的软件包

wget http://ftp.gnu.org/gnu/coreutils/coreutils-8.29.tar.xz


使用非root进行解压
tar -xvJf coreutils-8.29.tar.xz
cd coreutils-8.29
./configure
make
使用root进行make install

安装好之后,就可以写一个脚本,非常优雅的分布删除大文件,${i}G 表示,每次删除 10G

#!/bin/bash


TRUNCATE=/usr/local/bin/truncate
for i in `seq 2194 -10 10 `; 
do 
  sleep 2
  $TRUNCATE -s ${i}G /data/mysql/t_test.ibd.hdlk 
done
rm -rf /data/mysql/t_test.ibd.hdlk ;

最后,给大家一个建议,不要在业务高峰期做drop table操作,一定要在业务低峰期做。

(推荐微课:MySQL微课)

文章来源:www.toutiao.com/a6863864032139411975/

以上就是W3Cschool编程狮关于 如何drop掉mysql库中的1TB表单 的相关介绍了,希望对大家有所帮助。

学习Vue3.0,先从搭建环境开始

thbcm阅读(195)

本文将带您从零搭建一个基于Vue3.0viteVue3.0开发环境,通过本文的学习,你将学习到以下内容:

  1. 使用vite初始化Vue3.0项目
  2. 配置ts
  3. 配置vue-router
  4. 配置vuex
  5. 使用Vue3.0开发一个TodoList示例

使用vite初始化项目

vite 介绍

vite是尤大大在今年新鼓捣出来的一个工具,尤大大对vite的描述是这样的: Vite is an opinionated web dev build tool that serves your code via native ES Module imports during dev and bundles it with Rollup for production. 翻译成中文就是:Vite是一个由原生 ES Module驱动的 Web 开发构建工具。在开发环境下基于浏览器原生 ES imports 开发,在生产环境下基于Rollup打包。

上面这段话提到了一个关键字ES Module,这个是什么呢?详细的介绍大家可以访问 developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Modules 进行查看。此处我们长话短说。在最早的时候,还没有前端工程化,然后我们写javascript都是写到一个文件,然后通过script标签去引用,后来随着前端发展越来越壮大,js之间依赖越来越复杂,这时候就需要有一种可以将JavaScript 程序拆分为可按需导入的单独模块的机制来维护这个依赖,随之就诞生了AMD,CMD等等,而ES Module就是浏览器支持的原生模块依赖的功能。

为什么要用vite

为什么尤大大要推出vite,在我们使用webpack的时候,每次开发时候启动项目都需要几十秒甚至超过一分钟,比较慢,而且热更新也比较慢,而vite的主要特点就是快,官网对于vite的特点是这样描述的

  1. 快速的冷启动
  2. 即时的模块热更新
  3. 真正的按需编译

到底有多快呢,我们先新建一个项目试试

初始化vite项目

  1. 初始化项目, 在工作空间打开终端窗口,对于window用户即cmd,然后执行下面命令
   yarn create vite-app my-vue3

执行之后就会输出以下内容,可以看到新建项目特别快,仅仅用了1.63s

  1. 初始化完项目,通过cd my-vue3进行到项目里面,然后再执行yarn安装依赖(此处建议使用淘宝镜像,比较快)
  1. 依赖安装完需要通过yarn dev启动项目

是不是瞬间体验到了秒启项目的感觉,启动之后就可以通过http://localhost:3000来访问项目了

查看项目结构

使用vscode打开项目之后,可以查看到新建的项目结构与vue-cli4创建的项目结构基本一样,都是我们很熟悉的App.vuemain.js

查看main.js文件内容

打开main.js

import { createApp } from 'vue'
import App from './App.vue'
import './index.css'




createApp(App).mount('#app')

发现创建Vue的方式变了,原来是通过new Vue的方法来初始化Vue,在Vue3.0中,修改为了通过createApp的方式,关于Vue3.0的更多使用方式,我们将在后面的系列文章中逐渐为您带来讲解。

(推荐教程:Vue 2教程

配置typescript

typescript现在已经成为了前端必备技能之一,大量的项目也开始基于typescript进行开发。在使用Vue2.0的时候,因为Vue2.0没有对typescript进行支持,所以使用ts开发功能显示有些别扭。但到了Vue3,其自身源码便是基于ts开发的,所以对ts天生有着很好的支持。使用vite配置typescript很简单,只需要进行以下几步操作.

  1. 安装 typescript
   yarn add typescript -D

  1. 初始化tsconfig.json
   # 然后在控制台执行下面命令
   npx tsc --init

  1. main.js修改为main.ts,同时将index.html里面的引用也修改为main.ts, 通过还需要修改App.vueHelloWorld.vue文件,修改方式如下
   <!--将 <script> 修改为 <script lang="ts">-->
   <script lang="ts">
   import HelloWorld from './components/HelloWorld.vue'

   
   export default {
     name: 'App',
     components: {
       HelloWorld
     }
   }
   </script>

修改完之后,重启就可以访问项目了。虽然这样配置是可以了,但是打开main.ts会发现import App from App.vue会报错:Cannot find module './App.vue' or its corresponding type declarations.,这是因为现在ts还没有识别vue文件,需要进行下面的配置:

接下来你就可以开开心心的在组件中使用ts

    1. 在项目根目录添加shim.d.ts文件
  1. 添加以下内容
      declare module "*.vue" {
        import { Component } from "vue";
        const component: Component;
        export default component;
      }

配置 vue-router

Vue2.0中我们路由一般会选择使用vue-router,在Vue3.0依然可以使用vue-router,不过和Vue3.0一样当前vue-router的版本也是beta版本,在本文撰写的时候,版本是4.0.0-beta7

安装vue-router

因为当前vue-router针对vue3.0的版本还是beta版本,所以不能直接通过yarn add vue-router进行安装,而是需要带上版本号

yarn add vue-router@4.0.0-beta7

配置vue-router

在项目src目录下面新建router目录,然后添加index.ts文件,在文件中添加以下内容

import {createRouter, createWebHashHistory} from 'vue-router'




// 在 Vue-router新版本中,需要使用createRouter来创建路由
export default createRouter({
  // 指定路由的模式,此处使用的是hash模式
  history: createWebHashHistory(),
  // 路由地址
  routes: []
})

与新的Vue3.0初始化方式发生变化一样,vue-router的初始化方式也发生了变化,变成了通过createRouter来初始化路由。

router引入到main.ts

修改main.ts文件内容如下

import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import router from './router/index'




const  app = createApp(App)
// 通过use 将 路由插件安装到 app 中
app.use(router)
app.mount('#app')

配置 vuex

vue-router一样,新的vuex当前也处于beta版本,当前版本是4.0.0-beta.4

安装vuex

yarn add vuex@4.0.0-beta.4

配置vuex

在项目src目录下面新建store目录,并添加index.ts文件,文件中添加以下内容

import { createStore } from 'vuex'




interface State {
  userName: string
}




export default createStore({
  state(): State {
    return {
      userName: "子君",
    };
  },
});

引入到main.ts

import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import router from './router/index'
import store from './store/index'




const  app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')

开发TodoList

通过上面的一系列操作,我们的开发环境就已经配置完成了,接下来我们就通过新的开发环境先开发一个TodoList,来验证一下是否正常。

添加todolist页面

  1. 首先我们先在src目录下面新建一个views目录,然后在其中新建文件todo-list.vue,并为文件添加以下内容
   <template>
     <div class="todo-list">
       <div>
         <label>新增待办</label>
          <input v-model="state.todo" @keyup.enter="handleAddTodo">
       </div>
       <div>
         <h3>待办列表({{todos.length}})</h3>
         <ul>
           <li v-for="item in todos" :key="item.id" @click="handleChangeStatus(item, true)">
             <input type="checkbox">
             <label>{{item.text}}</label>
           </li>
         </ul>
       </div>
       <div><h3>已办列表({{dones.length}})</h3></div>
       <ul>
         <li v-for="item in dones" :key="item.id" @click="handleChangeStatus(item, false)">
             <input type="checkbox" checked>
             <label>{{item.text}}</label>
           </li>
       </ul>
     </div>
   </template>
   <script lang="ts">
    // 在vue2中 data 在vue3中使用 reactive代替
   import { reactive, computed } from 'vue'
   import { useRouter } from 'vue-router'
   export default {
     // setup相当于vue2.0的 beforeCreate和 created,是vue3新增的一个属性,所有的操作都在此属性中完成
     setup(props, context) {
       // 通过reactive 可以初始化一个可响应的数据,与Vue2.0中的Vue.observer很相似
       const state = reactive({
         todoList: [{
           id: 1,
           done: false,
           text: '吃饭'
         },{
           id: 2,
           done: false,
           text: '睡觉'
         },{
           id: 3,
           done: false,
           text: '打豆豆'
         }],
         todo: ''
       })
       // 使用计算属性生成待办列表
       const todos = computed(() => {
         return state.todoList.filter(item => !item.done)
       })

   
       // 使用计算属性生成已办列表
       const dones = computed(() => {
         return state.todoList.filter(item => item.done)
       })

   
       // 修改待办状态
       const handleChangeStatus = (item ,status) => {
         item.done = status
       }

       
       // 新增待办
       const handleAddTodo = () => {
         if(!state.todo) {
           alert('请输入待办事项')
           return
         }
         state.todoList.push({
           text: state.todo,
           id: Date.now(),
           done: false
         })
         state.todo = ''
       }

   
        // 在Vue3.0中,所有的数据和方法都通过在setup 中 return 出去,然后在template中使用
       return {
         state,
         todos,
         dones,
         handleChangeStatus,
         handleAddTodo
       }
     }
   }
   </script>
   <style scoped>
   .todo-list{
     text-align: center;
   }

   
   .todo-list ul li {
     list-style: none;
   }
   </style>

调整路由

a. 首先将App.vue文件内容修改为

<template> <router-view></router-view> </template>


<script lang="ts">


export default {
  name: 'App'
}
</script>

b. 然后修改 router/index.ts文件,添加新的路由

import {createRouter, createWebHashHistory} from ‘vue-router’


// 在 Vue-router新版本中,需要使用createRouter来创建路由
export default createRouter({
  // 指定路由的模式,此处使用的是hash模式
  history: createWebHashHistory(),
  // 路由地址
  routes: [{
    path: '/todolist',
    // 必须添加.vue后缀
    component: () => import('../views/todo-list.vue')
  }]
})

这时候我们就可以通过http://localhost:3000/#/todolist来访问TodoList了,效果如下图所示

(推荐微课:Vue 2.x 微课

总结

到此,我们Vue3.0的开发环境算是搭建完成了,当然现在还有好多好多要完善的东西,比如我们还需要去调整一下typescript的配置,然后添加eslint等等。同时如何在组件中跳转路由,使用vuex还没有去讲解,不过至少我们已经起步了。

文章来源:公众号–前端有的玩

作者:前端进击者

以上就是W3Cschool编程狮关于 学习Vue3.0,先从搭建环境开始 的相关介绍了,希望对大家有所帮助。

关于Node.js内存泄漏问题的分析

thbcm阅读(180)

本文将会先给大家介绍一些理论知识,随后再附上一个关于内存泄漏的案例,有兴趣的同学可以继续往下看。

Node.js 使用 V8 引擎,它的特点是会自动进行垃圾回收(Garbage Collection,GC),所以我们在写代码的时候就不需要像C/C++ 一样去手动分配、释放内存空间,方便不少,不过仍然需要注意内存的使用,避免造成内存泄漏(Memory Leak)。

从上图中,可以看到 Node.js 的常驻内存(Resident Set)分为堆和栈两个部分,具体为:

    • 指针空间(Old pointer space):存储的对象含有指向其它对象的指针。
    • 数据空间(Old data space):存储的对象仅含有数据(不含指向其它对象的指针),例如从新生代移动过来的字符串等。
    • 新生代(New Space/Young Generation):用来临时存储新对象,空间被等分为两份,整体较小,采用 Scavenge(Minor GC) 算法进行垃圾回收。
    • 老生代(Old Space/Old Generation):用来存储存活时间超过两个 Minor GC 时间的对象,采用 标记清除 & 整理(Mark-Sweep & Mark-Compact,Major GC) 算法进行垃圾回收,内部可再划分为两个空间:
    • 代码空间(Code Space):用于存放代码段,是唯一的可执行内存(不过过大的代码段也有可能存放在大对象空间)。
    • 大对象空间(Large Object Space):用于存放超过其它空间对象限制(Page::kMaxRegularHeapObjectSize)的大对象(可以参考这个 V8 Commit),存放在此的对象不会在垃圾回收的时候被移动。
  • 栈:用于存放原始的数据类型,函数调用时的入栈出栈也记录于此。

栈的空间由操作系统负责管理,开发者无需过于关心;堆的空间由 V8 引擎进行管理,可能由于代码问题出现内存泄漏,或者长时间运行后,垃圾回收导致程序运行速度变慢。

(推荐教程:Node入门

我们可以通过下面代码简单的观察 Node.js 内存使用情况:

const format = function (bytes) {
  return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
};


const memoryUsage = process.memoryUsage();


console.log(JSON.stringify({
    rss: format(memoryUsage.rss), // 常驻内存
    heapTotal: format(memoryUsage.heapTotal), // 总的堆空间
    heapUsed: format(memoryUsage.heapUsed), // 已使用的堆空间
    external: format(memoryUsage.external), // C++ 对象相关的空间
}, null, 2));

externalC++ 对象相关的空间,例如通过 new ArrayBuffer(100000); 申请一块 Buffer 内存的时候,可以明显看到 external 空间的增加。

可以通过下列参数调整相关空间的默认大小,单位为 MB:

  • –stack_size 调整栈空间
  • –min_semi_space_size 调整新生代半空间的初始值
  • –max_semi_space_size 调整新生代半空间的最大值
  • –max-new-space-size 调整新生代空间的最大值
  • –initial_old_space_size 调整老生代空间的初始值
  • –max-old-space-size 调整老生代空间的最大值

其中比较常用的是 --max_new_space_size--max-old-space-size

新生代的 Scavenge 回收算法、老生代的 Mark-Sweep & Mark-Compact 算法相关的文章已经很多,这里就不赘述了。

内存泄漏

由于不当的代码,有时候难免会发生内存泄漏,常见的有四个场景:

  1. 全局变量
  2. 闭包引用
  3. 事件绑定
  4. 缓存爆炸

接下来分别举个例子讲一讲。

全局变量

没有使用 var/let/const 声明的变量会直接绑定在 Global对象上(Node.js 中)或者 Windows 对象上(浏览器中),哪怕不再使用,仍不会被自动回收:

function test() {
  x = new Array(100000);
}


test();
console.log(x);

这段代码的输出为 [ <100000 empty items> ],可以看到 test 函数运行完后,数组 x 仍未被释放。

闭包引用

闭包引发的内存泄漏往往非常隐蔽,例如下面这段代码你能看出来是哪儿里有问题吗?

let theThing = null;
let replaceThing = function() {
  const newThing = theThing;
  const unused = function() {
    if (newThing) console.log("hi");
  };
  // 不断修改引用
  theThing = {
    longStr: new Array(1e8).join("*"),
    someMethod: function() {
      console.log("a");
    },
  };


  // 每次输出的值会越来越大
  console.log(process.memoryUsage().heapUsed);
};


setInterval(replaceThing, 100);

运行这段代码可以看到输出的已使用堆内存越来越大,而其中的关键就是因为 在目前的 V8 实现当中,闭包对象是当前作用域中的所有内部函数作用域共享的,也就是说 theThing.someMethodunUsed 共享同一个闭包的 context,导致 theThing.someMethod 隐式的持有了对之前的 newThing 的引用,所以会形成 theThing -> someMethod -> newThing -> 上一次 theThing ->...的循环引用,从而导致每一次执行 replaceThing 这个函数的时候,都会执行一次 longStr: new Array(1e8).join("*"),而且其不会被自动回收,导致占用的内存越来越大,最终内存泄漏。

对于上面这个问题有一个很巧妙的解决方法:通过引入新的块级作用域,将 newThing 的声明、使用与外部隔离开,从而打破共享,阻止循环引用。

let theThing = null;
let replaceThing = function() {
  {
    const newThing = theThing;
    const unused = function() {
      if (newThing) console.log("hi");
    };
  }
  // 不断修改引用
  theThing = {
    longStr: new Array(1e8).join("*"),
    someMethod: function() {
      console.log("a");
    },
  };


  console.log(process.memoryUsage().heapUsed);
};


setInterval(replaceThing, 100);

这里通过 { ... }形成了单独的块级作用域,而且在外部没有引用,从而 newThingGC 的时候会被自动回收,例如在我的电脑运行这段代码输出如下:

2097128
2450104
2454240
...
2661080
2665200
2086736 // 此时进行垃圾回收释放了内存
2093240

事件绑定

事件绑定导致的内存泄漏在浏览器中非常常见,一般是由于事件响应函数未及时移除,导致重复绑定或者 DOM 元素已移除后未处理事件响应函数造成的,例如下面这段 React 代码:

class Test extends React.Component {
  componentDidMount() {
    window.addEventListener('resize', function() {
      // 相关操作
    });
  }


  render() {
    return <div>test component</div>;
  }
}

<Test /> 组件在挂载的时候监听了 resize 事件,但是在组件移除的时候没有处理相应函数,假如 <Test /> 的挂载和移除非常频繁,那么就会在 window 上绑定很多无用的事件监听函数,最终导致内存泄漏。可以通过如下的方式避免这个问题:

class Test extends React.Component {
  componentDidMount() {
    window.addEventListener('resize', this.handleResize);
  }


  handleResize() { ... }


  componentWillUnmount() {
    window.removeEventListener('resize', this.handleResize);
  }


  render() {
    return <div>test component</div>;
  }
}

(推荐教程:Node.js教程

缓存爆炸

通过 Object/Map 的内存缓存可以极大地提升程序性能,但是很有可能未控制好缓存的大小和过期时间,导致失效的数据仍缓存在内存中,导致内存泄漏:

const cache = {};


function setCache() {
  cache[Date.now()] = new Array(1000);
}


setInterval(setCache, 100);

上面这段代码中,会不断的设置缓存,但是没有释放缓存的代码,导致内存最终被撑爆。

如果的确需要进行内存缓存的话,强烈建议使用 lru-cache这个 npm 包,可以设置缓存有效期和最大的缓存空间,通过 LRU 淘汰算法来避免缓存爆炸。

内存泄漏定位实操

当出现内存泄漏的时候,定位起来往往十分麻烦,主要有两个原因:

  1. 程序开始运行的时候,问题不会立即暴露,需要持续的运行一段时间,甚至一两天,才会复现问题。
  2. 出错的提示信息非常模糊,往往只能看到 heap out of memory 错误信息。

在这种情况下,可以借助两个工具来定问题:Chrome DevToolsheapdumpheapdump的作用就如同它的名字所说 – 将内存中堆的状态信息生成快照(snapshot)导出,然后我们将其导入到 Chrome DevTools 中看到具体的详情,例如堆中有哪些对象、占据多少空间等等。

接下来通过上文中闭包引用里内存泄漏的例子,来实际操作一把。首先 npm install heapdump 安装后,修改代码为下面的样子:

// 一段存在内存泄漏问题的示例代码
const heapdump = require('heapdump');


heapdump.writeSnapshot('init.heapsnapshot'); // 记录初始内存的堆快照


let i = 0; // 记录调用次数
let theThing = null;
let replaceThing = function() {
  const newThing = theThing;
  let unused = function() {
    if (newThing) console.log("hi");
  };


  // 不断修改引用
  theThing = {
    longStr: new Array(1e8).join("*"),
    someMethod: function() {
      console.log("a");
    },
  };


  if (++i >= 1000) {
    heapdump.writeSnapshot('leak.heapsnapshot'); // 记录运行一段时间后内存的堆快照
    process.exit(0);
  }
};


setInterval(replaceThing, 100);

在第 3 行和第 22 行,分别导出了初始状态的快照和循环了 1000 次后的快照,保存为 init.heapsnapshotleak.heapsnapshot

然后打开 Chrome 浏览器,按下 F12 调出DevTools 面板,点击 Memory 的 Tab,最后通过 Load 按钮将刚刚的两个快照依次导入:

导入后,在左侧可以看到堆内存有明显的上涨,从 1.7 MB 上涨到了 3.1 MB,几乎翻了一倍:

接下来就是最关键的步骤了,点击 leak 快照,然后将其与 init 快照进行对比:

右侧红框圈出来了两列:

  • Delta:表示变化的数量
  • Size Delta:表述变化的空间大小

可以看到增长最大的前两项是 拼接的字符串(concatenated string ) 和 闭包(closure),那么我们点开来看看具体有哪些:

从这两个图中,可以很直观的看出来主要是 theThing.someMethod 这个函数的闭包上下文和 theThing.longStr 这个很长的拼接字符串造成的内存泄漏,到这里问题就基本定位清楚了,我们还可以点击下方的 Object 模块来更清楚的看一下调用链的关系:

图中很明显的看出来,内存泄漏原因就是因为 newTHing <- 闭包上下文 <- someMethod<- 上一次 newThing 这样的链式依赖关系导致内存的快速增长。图中第二列的 distance 表示的是该变量距离根节点的距离,因而最上级的 newThing 是最远的,表示的是下级引用上级的关系。

(推荐微课:Node.js微课

文章来源:www.toutiao.com/a6863362442957849102/

以上就是W3Cschool编程狮关于关于Node.js内存泄漏问题的分析的相关介绍了,希望对大家有所帮助。

Spring官方宣布:Spring OAuth 2.0 授权服务器已经来临

thbcm阅读(188)

今年四月份Spring官方发起Spring Authorization Server项目。该项目是由Spring Security主导的一个社区驱动的、独立的孵化项目。

由于我们熟悉而且正在使用的Spring Security OAuth已经处在项目生命周期的尽头,Spring Authorization Server将替代Spring Security OAuthSpring 社区提供OAuth2.0授权服务器支持。

经过四个月的努力,Spring Authorization Server项目中的OAuth2.0授权服务器开发库正式发布了第一个版本。

昨天Spring Security官方发布消息:新的Spring 授权服务器已经来了!

目前你可以通过 repo.spring.io 或者Maven中央仓库获取到它,Maven坐标如下:

 <dependency>
     <groupId>org.springframework.security.experimental</groupId>
     <artifactId>spring-security-oauth2-authorization-server</artifactId>
     <version>0.0.1</version>
 </dependency>

初始版本特性:

  • OAuth 2.0 授权码模式 — RFC 6749
  • OAuth 2.0 客户端凭据模式 — RFC 6749
  • JSON Web Token (JWT) — RFC 7519
  • JSON Web Signature (JWS) — RFC 7515
  • JSON Web Key (JWK) — RFC 7517
  • 密钥管理,用于在签署JWT(JWS)时提供密钥

简化模式和密码模式目前没有实现。

2019年11月下旬,Spring官方在Spring Security OAuth 2.0路线图中 指出2.3.x版本将在2020年3月到达项目生命周期的终点(End Of Life),随后将会发布2.4.x和2.5.x。

后续2.4.x和2.5.x补丁和安全修复程序支持将持续到2021年5月,另外2.5.x的安全修复支持将持续到2022年5月项目终止日期。相同的寿命终止时间表适用于对应的Spring Boot 2自动配置项目Spring Security OAuth 2.0会在2022年5月项目终止后开放给Spring社区中的成员直接管理。

(推荐课程:Spring教程

文章来源:www.toutiao.com/a6864378555522875912/

以上就是W3Cschool编程狮关于Spring官方宣布:Spring OAuth 2.0 授权服务器已经来临的相关介绍了,希望对大家有所帮助。

如何使用代码体现一个浪漫七夕

thbcm阅读(195)

明天就是农历七月初七了,也就是我们传统的七夕节日要来了。七夕作为一个重大传统节日,它不仅仅是情侣专属,也合适单身人员表白,或许你也可以一起过节了呢。

你们准备好怎么和自己心仪的女童鞋表白了吗?已经有女朋友你们准备好礼物了吗?

作为程序猿兼码农的你们不表示一下吗?

心动

送一个爱心表示我对你心动,使用CSS3transform实现:

css 部分

html,body {
height:100%;
}
body {
margin:0;
padding:0;
background:#ffa5a5;
  display: flex;
  justify-content: center;
  align-items: center;
}
.chest {
width:500px;
height:500px;
  position:relative;
  display: flex;
  justify-content: center;
  align-items: center;
}
.heart {
    position:absolute;
    z-index:2;
    background:linear-gradient(-90deg,#F50A45 0%,#d5093c 40%);
    animation:beat 0.7s ease 0s infinite normal;
}
.heart.center {
    background:linear-gradient(-45deg,#B80734 0%,#d5093c 40%);
}
.heart.top {
    z-index:3;
}
.sided {
    top:100px;
    width:220px;
    height:220px;
    border-radius:110px;
}
.center {
    width:210px;
    height:210px;
    bottom:100px;
    left:145px;
    transform:rotateZ(225deg);
}
.left {
    left:62px;
}
.right {
    right:62px;
}
.title{
   z-index: 999;
   text-shadow: 5px 5px 5px #ff7f00;
}
@keyframes beat {
    0% {
    transform:scale(1) rotate(225deg);
    box-shadow:0 0 40px #d5093c;
}
50% {
    transform:scale(1.2) rotate(225deg);
    box-shadow:0 0 70px #d5093c;
}
100% {
    transform:scale(1) rotate(225deg);
    box-shadow:0 0 40px #d5093c;
}
}

html 部分

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>W3Cschool编程狮</title>
    <link href="./flower.css" type="text/css" rel="stylesheet">
</head>
<body>
    <div class="chest">
        <div class="heart left sided top"></div>
        <div class="heart center"></div>
        <div class="heart right sided"></div>
        <h1 class="title">W3Cschool编程狮</h1>
    </div>
</body>
</html>

玫瑰花

送一朵玫瑰表示我对你的爱,使用canvans画布实现:

<!DOCTYPE HTML>
<html>


<head>
    <title>Rose</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <style>
        html,
        body {
            height: 100%;
        }
    </style>
</head>


<body style="background:#000;margin:0;padding:0;display: flex;justify-content: center;align-items: center;">
    <canvas id="c" style="display:block"></canvas>
    <script>
        var b = document.body;
        var c = document.getElementsByTagName('canvas')[0];
        var a = c.getContext('2d');
        document.body.clientWidth;
    </script>
    <script>


        with (m = Math) C = cos, S = sin, P = pow, R = random;
        c.width = c.height = f = 600; h = -250;
        function p(a, b, c) {
            if (c > 60)
                return [S(a * 7) * (13 + 5 / (.2 + P(b * 4, 4))) - S(b) * 50, b * f + 50, 625 + C(a * 7) * (13 + 5 / (.2 + P(b * 4, 4))) + b * 400, a * 1 - b / 2, a];
            A = a * 2 - 1; B = b * 2 - 1;
            if (A * A + B * B < 1) {
                if (c > 37) { n = (j = c & 1) ? 6 : 4; o = .5 / (a + .01) + C(b * 125) * 3 - a * 300; w = b * h; return [o * C(n) + w * S(n) + j * 610 - 390, o * S(n) - w * C(n) + 550 - j * 350, 1180 + C(B + A) * 99 - j * 300, .4 - a * .1 + P(1 - B * B, -h * 6) * .15 - a * b * .4 + C(a + b) / 5 + P(C((o * (a + 1) + (B > 0 ? w : -w)) / 25), 30) * .1 * (1 - B * B), o / 1e3 + .7 - o * w * 3e-6] } if (c > 32) { c = c * 1.16 - .15; o = a * 45 - 20; w = b * b * h; z = o * S(c) + w * C(c) + 620; return [o * C(c) - w * S(c), 28 + C(B * .5) * 99 - b * b * b * 60 - z / 2 - h, z, (b * b * .3 + P((1 - (A * A)), 7) * .15 + .3) * b, b * .7] } o = A * (2 - b) * (80 - c * 2);
                w = 99 - C(A) * 120 - C(b) * (-h - c * 4.9) + C(P(1 - b, 7)) * 50 + c * 2;
                z = o * S(c) + w * C(c) + 700;
                return [o * C(c) - w * S(c), B * 99 - C(P(b, 7)) * 50 - c / 3 - z / 1.35 + 450, z, (1 - b / 1.2) * .9 + a * .1, P((1 - b), 20) / 4 + .05]
            }
        }
        setInterval('for(i=0;i<1e4;i++)if(s=p(R(),R(),i%46/.74)){z=s[2];x=~~(s[0]*f/z-h);y=~~(s[1]*f/z-h);if(!m[q=y*f+x]|m[q]>z)m[q]=z,a.fillStyle="rgb("+~(s[3]*h)+","+~(s[4]*h)+","+~(s[3]*s[3]*-80)+")",a.fillRect(x,y,1,1)}', 0)


    </script>
</body>

结束

在这里衷心祝大家七夕节日快乐,有女盆友的爱情美满比以前更甜蜜,没女盆友的早日脱单。

文章来源:公众号–小丑的小屋 作者:小丑

以上就是W3Cschool编程狮关于如何使用代码体现一个浪漫七夕的相关介绍了,希望对大家有所帮助。

阿里为什么推荐使用LongAdder,而不是volatile?

thbcm阅读(203)

阿里《Java开发手册》最新嵩山版在不久前发布,其中有一段内容引起了编者的注意,内容如下:

【参考】volatile 解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。

说明:如果是 count++ 操作,使用如下类实现:AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观 锁的重试次数)。

以上内容共有两个重点:

  1. 类似于 count++ 这种非一写多读的场景不能使用 volatile
  2. 如果是 JDK8 推荐使用 LongAdder 而非 AtomicLong 来替代 volatile,因为 LongAdder 的性能更好。

但口说无凭,即使是孤尽大佬说的,咱们也得证实一下,因为马老爷子说过:实践是检验真理的唯一标准

这样做也有它的好处,第一,加深了我们对知识的认知;第二,文档上只写了LongAdderAtomicLong 的性能高,但是高多少呢?文中并没有说,那只能我们自己动手去测试喽。

(推荐教程:Java教程

话不多,接下来我们直接进入本文正式内容…

volatile 线程安全测试

首先我们来测试 volatile 在多写环境下的线程安全情况,测试代码如下:

public class VolatileExample {
    public static volatile int count = 0; // 计数器
    public static final int size = 100000; // 循环测试次数


    public static void main(String[] args) {
        // ++ 方式 10w 次
        Thread thread = new Thread(() -> {
            for (int i = 1; i <= size; i++) {
                count++;
            }
        });
        thread.start();
        // -- 10w 次
        for (int i = 1; i <= size; i++) {
            count--;
        }
        // 等所有线程执行完成
        while (thread.isAlive()) {}
        System.out.println(count); // 打印结果
    }
}

我们把 volatile 修饰的 count 变量 ++ 10w 次,在启动另一个线程 — 10w 次,正常来说结果应该是 0,但是我们执行的结果却为:

1063

结论:由以上结果可以看出 volatile 在多写环境下是非线程安全的,测试结果和《Java开发手册》相吻合。

LongAdder VS AtomicLong

接下来,我们使用 Oracle 官方的 JMH 来测试一下两者的性能,测试代码如下:

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;


import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;


@BenchmarkMode(Mode.AverageTime) // 测试完成时间
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 1 轮,每次 1s
@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 3s
@Fork(1) // fork 1 个线程
@State(Scope.Benchmark)
@Threads(1000) // 开启 1000 个并发线程
public class AlibabaAtomicTest {


    public static void main(String[] args) throws RunnerException {
        // 启动基准测试
        Options opt = new OptionsBuilder()
                .include(AlibabaAtomicTest.class.getSimpleName()) // 要导入的测试类
                .build();
        new Runner(opt).run(); // 执行测试
    }


    @Benchmark
    public int atomicTest(Blackhole blackhole) throws InterruptedException {
        AtomicInteger atomicInteger = new AtomicInteger();
        for (int i = 0; i < 1024; i++) {
            atomicInteger.addAndGet(1);
        }
        // 为了避免 JIT 忽略未被使用的结果
        return atomicInteger.intValue();
    }


    @Benchmark
    public int longAdderTest(Blackhole blackhole) throws InterruptedException {
        LongAdder longAdder = new LongAdder();
        for (int i = 0; i < 1024; i++) {
            longAdder.add(1);
        }
        return longAdder.intValue();
    }
}

程序执行的结果为:

从上述的数据可以看出,在开启了 1000 个线程之后,程序的 LongAdder 的性能比 AtomicInteger 快了约 1.53 倍,你没看出是开了 1000 个线程,为什么要开这么多呢?这其实是为了模拟高并发高竞争的环境下二者的性能查询。

如果在低竞争下,比如我们开启 100 个线程,测试的结果如下:

结论:从上面结果可以看出,在低竞争的并发环境下 AtomicInteger的性能是要比 LongAdder 的性能好,而高竞争环境下 LongAdder的性能比 AtomicInteger,当有 1000 个线程运行时,LongAdder 的性能比 AtomicInteger 快了约 1.53 倍,所以各位要根据自己业务情况选择合适的类型来使用。

性能分析

为什么会出现上面的情况?这是因为 AtomicInteger 在高并发环境下会有多个线程去竞争一个原子变量,而始终只有一个线程能竞争成功,而其他线程会一直通过 CAS 自旋尝试获取此原子变量,因此会有一定的性能消耗;而 LongAdder 会将这个原子变量分离成一个 Cell 数组,每个线程通过 Hash 获取到自己数组,这样就减少了乐观锁的重试次数,从而在高竞争下获得优势;而在低竞争下表现的又不是很好,可能是因为自己本身机制的执行时间大于了锁竞争的自旋时间,因此在低竞争下表现性能不如 AtomicInteger

(推荐微课:Java微课

总结

本文我们测试了 volatile 在多写情况下是非线程安全的,而在低竞争的并发环境下 AtomicInteger 的性能是要比 LongAdder 的性能好,而高竞争环境下 LongAdder 的性能比 AtomicInteger,因此我们在使用时要结合自身的业务情况来选择相应的类型。

文章来源:公众号– Java中文社群 作者:磊哥

以上就是W3Cschool编程狮关于阿里为什么推荐使用LongAdder,而不是volatile?的相关介绍了,希望对大家有所帮助。

6种对象复制工具类,该如何选择呢?

thbcm阅读(227)

在日常工作中,我们难免会碰到对象属性复制的时候,下面就一个常见的三层 MVC 架构举例。

当我们在上面的架构下编程时,我们通常需要经历对象转化,比如业务请求流程经历三层机构后需要把 DTO 转为DO然后在数据库中保存。

当需要从数据查询数据页面展示时,查询数据经过三层架构将会从 DO 转为 DTO,最后再转为 VO,然后在页面中展示。

当业务简单的时候,我们手写代码,通过 getter/setter复制对象属性,十分简单。但是一旦业务变的复杂,对象属性变得很多,那么手写代码就会成为程序员的噩梦。

不但手写十分繁琐,非常耗时间,并且还可能容易出错。

小编之前就经历过一个项目,一个对象中大概有四五十个字段属性,那时候小编还刚入门,什么都不太懂,写了半天 getter/setter复制对象属性。

话外音:一个对象属性这么多,显然是不太合理的,我们设计过程应该将其拆分。

直到后来,小编了解到了对象属性复制工具类,使用之后,发现是真香,再也不用手写代码。再后来,碰到越来越多工具类,虽然核心功能都是一样的,但是还是存在很多差异。新手看到可能会一脸懵逼,不知道如何选择。

所以小编今天这篇介绍一下市面上常用的工具类:

  • Apache BeanUtils
  • Spring BeanUtils
  • Cglib BeanCopier
  • Dozer
  • orika
  • MapStruct

工具类特性

在介绍这些工具类之前,我们来看下一个好用的属性复制工具类,需要有哪些特性:

  • 基本属性复制,这个是基本功能
  • 不同类型的属性赋值,比如基本类型与其包装类型等
  • 不同字段名属性赋值,当然字段名应该尽量保持一致,但是实际业务中,由于不同开发人员,或者笔误拼错单词,这些原因都可能导致会字段名不一致的情况
  • 浅拷贝/深拷贝,浅拷贝会引用同一对象,如果稍微不慎,同时改动对象,就会踩到意想不到的坑

下面我们开始介绍工具类。

(推荐教程:Java教程

Apache BeanUtils

首先介绍是第一位应该是 Java 领域属性复制的最有名的工具类「Apache BeanUtils」,这个工具类想必很多人或多或少用过或则见过。

没用过也没关系,我们来展示这个类的用法,用法非常简单。

首先我们引入依赖,这里使用最新版本:

<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.4</version>
</dependency>

假设我们项目中有如下类:

此时我们需要完成 DTO 对象转化到 DO 对象,我们只需要简单调用BeanUtils#copyProperties 方法就可以完成对象属性的复制。

StudentDTO studentDTO = new StudentDTO();
studentDTO.setName("小编");
studentDTO.setAge(18);
studentDTO.setNo("6666");


List<String> subjects = new ArrayList<>();
subjects.add("math");
subjects.add("english");
studentDTO.setSubjects(subjects);
studentDTO.setCourse(new Course("CS-1"));
studentDTO.setCreateDate("2020-08-08");


StudentDO studentDO = new StudentDO();


BeanUtils.copyProperties(studentDO, studentDTO);

不过,上面的代码如果你这么写,我们会碰到第一个问题,BeanUtils默认不支持 String转为 Date 类型。

为了解决这个问题,我们需要自己构造一个 Converter 转换类,然后使用 ConvertUtils注册,使用方法如下:

ConvertUtils.register(new Converter() {
    @SneakyThrows
    @Override
    public <Date> Date convert(Class<Date> type, Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof String) {
            String str = (String) value;
            return (Date) DateUtils.parseDate(str, "yyyy-MM-dd");
        }
        return null;




    }
}, Date.class);

此时,我们观察 studentDOstudentDTO对象属性值:

从上面的图我们可以得出BeanUtils一些结论:

  • 普通字段名不一致的属性无法被复制
  • 嵌套对象字段,将会与源对象使用同一对象,即使用浅拷贝
  • 类型不一致的字段,将会进行默认类型转化。

虽然 BeanUtils 使用起来很方便,不过其底层源码为了追求完美,加了过多的包装,使用了很多反射,做了很多校验,所以导致性能较差,所以并阿里巴巴开发手册上强制规定避免使用 Apache BeanUtils

Spring BeanUtils

Spring 属性复制工具类类名与 Apache 一样,基本用法也差不多。我先来看下 Spring BeanUtils 基本用法。

同样,我们先引入依赖,从名字我们可以看出,BeanUtils 位于 Spring-Beans 模块,这里我们依然使用最新模块。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>

这里我们使用 DTO 与 DO 复用上面的例子,转换代码如下:

// 省略上面赋值代码,与上面一致
StudentDO studentDO = new StudentDO();


BeanUtils.copyProperties(studentDTO, studentDO);

从用法可以看到,Spring BeanUtils 与 Apache 有一个最大的不同,两者源对象与目标对象参数位置不一样,小编之前没注意,用了 Spring 工具类,但是却是按照 Apache 的用法使用。

此时对比studentDOstudentDTO对象:

从上面的对比图我们可以得到一些结论:

  • 字段名不一致,属性无法复制
  • 类型不一致,属性无法复制。但是注意,如果类型为基本类型以及基本类型的包装类,这种可以转化
  • 嵌套对象字段,将会与源对象使用同一对象,即使用浅拷贝

除了这个方法之外,Spring BeanUtils 还提供了一个重载方法:

public static void copyProperties(Object source, Object target, String... ignoreProperties) 

使用这个方法,我们可以忽略某些不想被复制过去的属性:

BeanUtils.copyProperties(studentDTO, studentDO,"name");

这样,name 属性就不会被复制到 DO 对象中。

虽然 Spring BeanUtils 与 Apache BeanUtils 功能差不多,但是在性能上 Spring BeanUtils 还是完爆 Apache BeanUtils。主要原因还是在于 Spring 并没有与 Apache 一样使用反射做了过多校验,另外 Spring BeanUtils 内部使用了缓存,加快转换的速度。

所以两者选择,还是推荐使用 Spring BeanUtils。

Cglib BeanCopier

上面两个是小编日常工作经常使用,而下面的这些都是小编最近才开始接触的,比如 Cglib BeanCopier。这个使用方法,可能比上面两个类稍微复杂一点,下面我们来看下具体用法:

首先我们引入 Cglib 依赖:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

画外音:如果你工程内还有 Spring-Core 的话,如果查找BeanCopier 这个类,可以发现两个不同的包的同名类。

一个属于 Cglib,另一个属于 Spring-Core。

其实 Spring-Core 内BeanCopier 实际就是引入了 Cglib 中的类,这么做的目的是为包了保证 Spring 使用长度 Cglib 相关类的稳定性,防止外部 Cglib 依赖不一致,导致 Spring 运行异常。

转换代码如下:

// 省略赋值语句
StudentDO studentDO = new StudentDO();
BeanCopier beanCopier = BeanCopier.create(StudentDTO.class, StudentDO.class, false);
beanCopier.copy(studentDTO, studentDO, null);

使用方法相比 BeanUtils, BeanCopier 稍微多了一步。 对比studentDOstudentDTO对象:

从上面可以得到与 Spring Beanutils 基本一致的结论:

  • 字段名不一致,属性无法复制
  • 类型不一致,属性无法复制。不过有点不一样,如果类型为基本类型/基本类型的包装类型,这两者无法被拷贝。
  • 嵌套对象字段,将会与源对象使用同一对象,即使用浅拷贝

上面我们使用 Beanutils,遇到这种字段名,类型不一致的这种情况,我们没有什么好办法,只能手写硬编码。

不过在 BeanCopier 下,我们可以引入转换器,进行类型转换。

// 注意最后一个属性设置为 true
BeanCopier beanCopier = BeanCopier.create(StudentDTO.class, StudentDO.class, true);
// 自定义转换器
beanCopier.copy(studentDTO, studentDO, new Converter() {
    @Override
    public Object convert(Object source, Class target, Object context) {
        if (source instanceof Integer) {
            Integer num = (Integer) source;
            return num.toString();
        }
        return null;
    }
});

不过吐槽一下这个转换器,一旦我们自己打开使用转换器,所有属性复制都需要我们自己来了。比如上面的例子中,我们只处理当源对象字段类型为 Integer,这种情况,其他都没处理。我们得到 DO 对象将会只有 name 属性才能被复制。

Cglib BeanCopier 的原理与上面两个 Beanutils 原理不太一样,其主要使用 字节码技术动态生成一个代理类,代理类实现get 和 set方法。生成代理类过程存在一定开销,但是一旦生成,我们可以缓存起来重复使用,所有 Cglib 性能相比以上两种 Beanutils 性能比较好。

Dozer

Dozer ,中文直译为挖土机 ,这是一个「重量级」属性复制工具类,相比于上面介绍三个工具类,Dozer 具有很多强大的功能。

画外音:重量级/轻量级其实只是一个相对的说法,由于 Dozer 相对 BeanUtils 这类工具类来说,拥有许多高级功能,所以相对来说这是一个重量级工具类。

小编刚碰到这个工具类,就被深深折服,真的太强大了,上面我们期望的功能,Dozer 都给你实现了。

下面我们来看下使用方法,首先我们引入 Dozer 依赖:

<dependency>
  <groupId>net.sf.dozer</groupId>
  <artifactId>dozer</artifactId>
  <version>5.4.0</version>
</dependency>

使用方法如下:

// 省略属性的代码
DozerBeanMapper mapper = new DozerBeanMapper();
StudentDO studentDO =
        mapper.map(studentDTO, StudentDO.class);
System.out.println(studentDO);

Dozer 需要我们新建一个DozerBeanMapper,这个类作用等同与 BeanUtils,负责对象之间的映射,属性复制。

画外音:下面的代码我们可以看到,生成 DozerBeanMapper实例需要加载配置文件,随意生成代价比较高。在我们应用程序中,应该使用单例模式,重复使用DozerBeanMapper

如果属性都是一些简单基本类型,那我们只要使用上面代码,可以快速完成属性复制。

不过很不幸,我们的代码中有字符串与 Date 类型转化,如果我们直接使用上面的代码,程序运行将会抛出异常。

所以这里我们要用到 Dozer 强大的配置功能,我们总共可以使用下面三种方式:

  • XML
  • API
  • 注解

其中,API 的方式比较繁琐,目前大部分使用 XML 进行,另外注解功能的是在 Dozer 5.3.2 之后增加的新功能,不过功能相较于 XML 来说较弱。

XML 使用方式

下面我们使用 XML 配置方式,配置 DTO 与 DO 关系,首先我们新建一个dozer/dozer-mapping.xml 文件:

<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns="http://dozer.sourceforge.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://dozer.sourceforge.net
          http://dozer.sourceforge.net/schema/beanmapping.xsd">
    <!-- 类级别的日期转换,默认使用这个格式转换    -->
    <mapping date-format="yyyy-MM-dd HH:mm:ss">
        <class-a>com.just.doone.example.domain.StudentDTO</class-a>
        <class-b>com.just.doone.example.domain.StudentDO</class-b>
        <!-- 在下面指定字段名不一致的映射关系       -->
        <field>
            <a>no</a>
            <b>number</b>
        </field>


        <field>
            <!-- 字段级别的日期转换,将会覆盖字段上的转换            -->
            <a date-format="yy-MM-dd">createDate</a>
            <b>createDate</b>
        </field>
    </mapping>
</mappings>

然后修改我们的 Java 代码,增加读取 Dozer 的配置文件:

DozerBeanMapper mapper = new DozerBeanMapper();
List<String> mappingFiles = new ArrayList<>();
// 读取配置文件
mappingFiles.add("dozer/dozer-mapping.xml");
mapper.setMappingFiles(mappingFiles);
StudentDO studentDO = mapper.map(studentDTO, StudentDO.class);
System.out.println(studentDO);

运行之后,对比studentDOstudentDTO对象:

从上面的图我们可以发现:

  • 类型不一致的字段,属性被复制
  • DO 与 DTO 对象字段不是同一个对象,也就是深拷贝
  • 通过配置字段名的映射关系,不一样字段的属性也被复制

除了上述这些相对简单的属性以外,Dozer 还支持很多额外的功能,比如枚举属性复制,Map 等集合属性复制等。

有些小伙伴刚看到 Dozer 的用法,可能觉得这个工具类比较繁琐,不像 BeanUtils 工具类一样一行代码就可以解。

其实 Dozer 可以很好跟 Spring 框架整合,我们可以在 Spring 配置文件提前配置,后续我们只要引用 Dozer 的相应的 Bean ,使用方式也是一行代码。

Dozer 与 Spring 整合,我们可以使用其 DozerBeanMapperFactoryBean,配置如下:

    <bean class="org.dozer.spring.DozerBeanMapperFactoryBean">
        <property name="mappingFiles" 
                  value="classpath*:/*mapping.xml"/>
      <!--自定义转换器-->
        <property name="customConverters">
            <list>
                <bean class=
                      "org.dozer.converters.CustomConverter"/>      
            </list>
        </property>
    </bean>

DozerBeanMapperFactoryBean支持设置属性比较多,可以自定义设置类型转换,还可以设置其他属性。

另外还有一种简单的方法,我们可以在 XML 中配置DozerBeanMapper

    <bean id="org.dozer.Mapper" class="org.dozer.DozerBeanMapper">
        <property name="mappingFiles">
            <list>
                <value>dozer/dozer-Mapperpping.xml</value>
            </list>
        </property>
    </bean>

Spring 配置完成之后,我们在代码中可以直接注入:

@Autowired
Mapper mapper;


public void objMapping(StudentDTO studentDTO) {
// 直接使用
StudentDO studentDO =
mapper.map(studentDTO, StudentDO.class);
}

注解方式

Dozer 注解方式相比 XML 配置来说功能很弱,只能完成字段名不一致的映射。

上面的代码中,我们可以在 DTO 的 no 字段上使用 @Mapping 注解,这样我们在使用 Dozer 完成转换时,该字段属性将会被复制。

@Data
public class StudentDTO {


    private String name;


    private Integer age;
    @Mapping("number")
    private String no;


    private List<String> subjects;


    private Course course;
    private String createDate;
}

虽然目前注解功能有点薄弱,不过后看版本官方可能增加新的注解功能,另外 XML 与注解可以一起使用。

最后 Dozer 底层本质上还是使用了反射完成属性的复制,所以执行速度并不是那么理想。

orika

orika也是一个跟 Dozer 类似的重量级属性复制工具类,也提供诸如 Dozer 类似的功能。但是 orika 无需使用繁琐 XML 配置,它自身提供一套非常简洁的 API 用法,非常容易上手。

首先我们引入其最新的依赖:

<dependency>
    <groupId>ma.glasnost.orika</groupId>
    <artifactId>orika-core</artifactId>
    <version>1.5.4</version>
</dependency>

基本使用方法如下:

// 省略其他设值代码


// 这里先不要设值时间
// studentDTO.setCreateDate("2020-08-08");


MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
MapperFacade mapper = mapperFactory.getMapperFacade();
StudentDO studentDO = mapper.map(studentDTO, StudentDO.class);

这里我们引入两个类 MapperFactoryMapperFacade,其中 MapperFactory 可以用于字段映射,配置转换器等,而 MapperFacade 的作用就与 Beanutils 一样,用于负责对象的之间的映射。

上面的代码中,我们故意注释了 DTO 对象中的 createDate 时间属性的设值,这是因为默认情况下如果没有单独设置时间类型的转换器,上面的代码将会抛错。

另外,上面的代码中,对于字段名不一致的属性,是不会复制的,所以我们需要单独设置。

下面我们就设置一个时间转换器,并且指定一下字段名:

MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
ConverterFactory converterFactory = mapperFactory.getConverterFactory();
converterFactory.registerConverter(new DateToStringConverter("yyyy-MM-dd"));
mapperFactory.classMap(StudentDTO.class, StudentDO.class)
        .field("no", "number")
      // 一定要调用下 byDefault
        .byDefault()
        .register();
MapperFacade mapper = mapperFactory.getMapperFacade();
StudentDO studentDO = mapper.map(studentDTO, StudentDO.class);

上面的代码中,首先我们需要在 ConverterFactory 注册一个时间类型的转换器,其次我们还需要再 MapperFactory 指定不同字段名的之间的映射关系。

这里我们要注意,在我们使用 classMap 之后,如果想要相同字段名属性默认被复制,那么一定调用 byDefault方法。

简单对比一下 DTO 与 DO 对象:

上图可以发现 orika 的一些特性:

  • 默认支持类型不一致(基本类型/包装类型)转换
  • 支持深拷贝
  • 指定不同字段名映射关系,属性可以被成功复制。

另外 orika 还支持集合映射:

MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
List<Person> persons = new ArrayList<>();
List<PersonDto> personDtos = mapperFactory.getMapperFacade().mapAsList(persons, PersonDto.class);

最后聊下 orika 实现原理,orika 与 dozer 底层原理不太一样,底层其使用了 javassist 生成字段属性的映射的字节码,然后直接动态加载执行字节码文件,相比于 Dozer 的这种使用反射原来的工具类,速度上会快很多。

MapStruct

不知不觉,一口气已经写了 5 个属性复制工具类,小伙伴都看到这里,那就不要放弃了,坚持看完,下面将介绍一个与上面这些都不太一样的工具类「MapStruct」。

上面介绍的这些工具类,不管使用反射,还是使用字节码技术,这些都需要在代码运行期间动态执行,所以相对于手写硬编码这种方式,上面这些工具类执行速度都会慢很多。

那有没有一个工具类的运行速度与硬编码这种方式差不多那?

这就要介绍 MapStruct 这个工具类,这个工具类之所以运行速度与硬编码差不多,这是因为他在编译期间就生成了 Java Bean 属性复制的代码,运行期间就无需使用反射或者字节码技术,所以确保了高性能。

另外,由于编译期间就生成了代码,所以如果有任何问题,编译期间就可以提前暴露,这对于开发人员来讲就可以提前解决问题,而不用等到代码应用上线了,运行之后才发现错误。

下面我们来看下,怎么使用这个工具类,首先我们先引入这个依赖:

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.3.1.Final</version>
</dependency>

其次,由于 MapStruct 需要在编译器期间生成代码,所以我们需要 maven-compiler-plugin插件中配置:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.1</version>
    <configuration>
        <source>1.8</source> <!-- depending on your project -->
        <target>1.8</target> <!-- depending on your project -->
        <annotationProcessorPaths>
            <path>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.3.1.Final</version>
            </path>
            <!-- other annotation processors -->
        </annotationProcessorPaths>
    </configuration>
</plugin>

接下来我们需要定义映射接口,代码如下:

@Mapper
public interface StudentMapper {


    StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);

  
    @Mapping(source = "no", target = "number")
    @Mapping(source = "createDate", target = "createDate", dateFormat = "yyyy-MM-dd")
    StudentDO dtoToDo(StudentDTO studentDTO);
}

我们需要使用 MapStruct 注解 @Mapper 定义一个转换接口,这样定义之后,StudentMapper 的功能就与 BeanUtils 等工具类一样了。

其次,由于我们 DTO 与 DO 对象中存在字段名不一致的情况,所以我们还在在转换方法上使用 @Mapping 注解指定字段映射。另外我们 createDate 字段类型不一致,这里我们还需要指定时间格式化类型。

上面定义完成之后,我们就可以直接使用 StudentMapper 一行代码搞定对象转换。

// 忽略其他代码
StudentDO studentDO = StudentMapper.INSTANCE.dtoToDo(studentDTO);

如果我们对象使用 Lombok 的话,使用 @Mapping指定不同字段名,编译期间可能会抛出如下的错误:

这个原因主要是因为 Lombok 也需要编译期间自动生成代码,这就可能导致两者冲突,当 MapStruct 生成代码时,还不存在 Lombok 生成的代码。

解决办法可以在 maven-compiler-plugin插件配置中加入 Lombok,如下:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.1</version>
    <configuration>
        <source>1.8</source> <!-- depending on your project -->
        <target>1.8</target> <!-- depending on your project -->
        <annotationProcessorPaths>
            <path>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.3.1.Final</version>
            </path>
            <path>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.12</version>
            </path>
            <!-- other annotation processors -->
        </annotationProcessorPaths>
    </configuration>
</plugin>

输出 DO 与 DTO 如下:

从上图中我们可以得到一些结论:

  • 部分类型不一致,可以自动转换,比如
    • 基本类型与包装类型
    • 基本类型的包装类型与 String
  • 深拷贝

上面介绍的例子介绍一些简单字段映射,如果小伙伴在工作总共还碰到其他的场景,可以先查看一下这个工程,查看一下有没有结局解决办法:github.com/mapstruct/mapstruct-examples

上面我们已经知道 MapStruct 在编译期间就生成了代码,下面我们来看下自动生成代码:

public class StudentMapperImpl implements StudentMapper {
    public StudentMapperImpl() {
    }


    public StudentDO dtoToDo(StudentDTO studentDTO) {
        if (studentDTO == null) {
            return null;
        } else {
            StudentDO studentDO = new StudentDO();
            studentDO.setNumber(studentDTO.getNo());


            try {
                if (studentDTO.getCreateDate() != null) {
                    studentDO.setCreateDate((new SimpleDateFormat("yyyy-MM-dd")).parse(studentDTO.getCreateDate()));
                }
            } catch (ParseException var4) {
                throw new RuntimeException(var4);
            }


            studentDO.setName(studentDTO.getName());
            if (studentDTO.getAge() != null) {
                studentDO.setAge(String.valueOf(studentDTO.getAge()));
            }


            List<String> list = studentDTO.getSubjects();
            if (list != null) {
                studentDO.setSubjects(new ArrayList(list));
            }


            studentDO.setCourse(studentDTO.getCourse());
            return studentDO;
        }
    }
}

从生成的代码来看,里面并没有什么黑魔法,MapStruct 自动生成了一个实现类 StudentMapperImpl,里面实现了 dtoToDo,方法里面调用getter/setter设值。

从这个可以看出,MapStruct 作用就相当于帮我们手写getter/setter设值,所以它的性能会很好。

(推荐微课:Java微课

总结

看文这篇文章,我们一共学习了 7 个属性复制工具类,这么多工具类我们该如何选择那?小编讲讲自己的一些见解:

第一,首先我们直接抛弃 Apache Beanutils ,这个不用说了,阿里巴巴规范都这样定了,我们就不要使用好了。

第二,当然是看工具类的性能,这些工具类的性能,网上文章介绍的比较多,小编就复制过来,大家可以比较一下。

可以看到 MapStruct 的性能可以说还是相当优秀。那么如果你的业务对于性能,响应等要求比较高,或者你的业务存在大数据量导入/导出的场景,而这个代码存在对象转化,那就切勿使用 Apache Beanutils, Dozer 这两个工具类。

第三,其实很大一部分应用是没有很高的性能的要求,只要工具类能提供足够的便利,就可以接受。如果你的业务中没有很复杂的的需求,那么直接使用 Spring Beanutils 就好了,毕竟 Spring 的包大部分应用都在使用,我们都无需导入其他包了。

那么如果业务存在不同类型,不同的字段名,那么可以考虑使用 orika 等这种重量级工具类。

文章来源:公众号–Java极客技术 作者:鸭血粉丝

以上就是W3Cschool编程狮关于 6种对象复制工具类,该如何选择呢?的相关介绍了,希望对大家有所帮助。

12题精选的CSS面试题(含解析)

thbcm阅读(192)

本文收集整理了12道关于CSS的面试题,大家一起来看看吧。

1.在 css 选择器当中,优先级排序正确的是()

A、id选择器>标签选择器>类选择器

B、标签选择器>类选择器>id选择器

C、类选择器>标签选择器>id选择器

D、id选择器>类选择器>标签选择器

4个等级的定义如下:

第一等:代表内联样式,如: style=””,权值为1000

第二等:代表ID选择器,如:#content,权值为100

第三等:代表类,伪类和属性选择器,如.content,权值为10

第四等:代表类型选择器和伪元素选择器,如div p,权值为1

2.下列定义的 css 中,哪个权重是最低的?( )

A、#game .name

B、#game .name span

C、#game div

D、#game div.name

权重越大,优先级越高

CSS选择器优先级是指基础选择器的优先级:

ID` > `CLASS` > `ELEMENT` > `*

3、关于HTML语义化,以下哪个说法是正确的?( )

A、语义化的HTML有利于机器的阅读,如PDA手持设备、搜索引擎爬虫;但不利于人的阅读

B、Table 属于过时的标签,遇到数据列表时,需尽量使用 div 来模拟表格

C、语义化是HTML5带来的新概念,此前版本的HTML无法做到语义化

D、headerarticleaddress都属于语义化明确的标签

解析:

选D

1、什么是HTML语义化?

根据内容的结构化(内容语义化),选择合适的标签(代码语义化)便于开发者阅读和写出更优雅的代码的同时让浏览器的爬虫和机器很好地解析。

2、为什么要语义化?

为了在没有CSS的情况下,页面也能呈现出很好地内容结构、代码结构

用户体验:例如titlealt用于解释名词或解释图片信息、label标签的活用;

有利于SEO :和搜索引擎建立良好沟通,有助于爬虫抓取更多的有效信息:

爬虫依赖于标签来确定上下文和各个关键字的权重;方便其他设备解析(如屏幕阅读器、盲人阅读器、移动设备)以意义的方式来渲染网页;

便于团队开发和维护,语义化更具可读性,是下一步吧网页的重要动向,遵循W3C标准的团队都遵循这个标准,可以减少差异化

4、CSS 样式,下面哪一个元素能够达到最大宽度,且前后各有一个换行?( )

A、Block Element

B、Square Element

C、Side Element

D、Box Element

解析:

选A

块级元素block element

行内元素 inline element

行内块元素inline-block element

5、放在HTML里的哪一部分JavaScripts会在页面加载的时候被执行?( )

A、文件头部位置

B、文件尾

C、<head>标签部分

D、<body>标签部分

解析:

选D

head部分中的JavaScripts会在被调用的时候才执行。

body部分中的JavaScripts会在页面加载的时候被执行。

6、下列说法正确的有:( )

A、visibility:hidden;所占据的空间位置仍然存在,仅为视觉上的完全透明;

B、display:none;不为被隐藏的对象保留其物理空间;

C、visibility:hidden;与display:none;两者没有本质上的区别;

D、visibility:hidden;产生reflowrepaint(回流与重绘);

AB

visiblity:看不见,摸的着.

display:看不见,摸不着.

displaydom级别的,可以渲染和重绘。

visiblity不是dom级别的,不能重绘,只能渲染

7、新窗口打开网页,用到以下哪个值( )

A、_self

B、_blank

C、_top

D、_parent

解析:

B

html中通过<a>标签打开一个链接,通过 <a> 标签的 target

属性规定在何处打开链接文档。

如果在标签<a>中写入target属性,则浏览器会根据target的属性值去打开与其命名或名称相符的 框架<frame>或者窗口.

target中还存在四个保留的属性值如下,

_black:在新窗口中打开被链接文档

_self:默认。在相同的框架中打开被链接文档

_ parent:在父框架中打开被链接文档

_top:在整个窗口中打开被链接文档

framename:在指定框架中打开被链接文档

8、下列说法错误的是( )

A、设置display:none后的元素只会导致浏览器的重排而不会重绘

B、设置visibility:hidde后的元素只会导致浏览器重绘而不会重排

C、设置元素opacity:0之后,也可以触发点击事件

D、visibility:hidden的元素无法触发其点击事件

解析:

A

设置display:none后的元素会导致浏览器的重排重绘

9、超链接访问过后hover样式就不出现了,被点击访问过的超链接样式不再具有hoveractive了,解决方法是改变CSS属性的排列顺序?( )

A、a:link {} a:visited {} a:hover {} a:active {}

B、a:visited {} a:link {} a:hover {} a:active {}

C、a:active {} a:link {} a:hover {} a:visited {}

D、a:link {} a:active {} a:hover {} a:visited {}

解析:

A

a:link`; `a:visited`; `a:hover`; `a:active;

a:hover必须放在a:linka:visited之后;

a:active必须放在a:hover之后。

10、关于position定位,下列说法错误的是( )

A、fixed元素,可定位于相对于浏览器窗口的指定坐标,它始终是以 body 为依据

B、relative元素以它原来的位置为基准偏移,在其移动后,原来的位置不再占据空间

C、absolute 的元素,如果它的 父容器设置了 position 属性,并且 position 的属性值为 absolute 或者 relative,那么就会依据父容器进行偏移

D、fixed 属性的元素在标准流中不占位置

解析:

B

absolute:生成绝对定位的元素,相对于static定位以外的第一个父元素进行定位,元素的位置通过lefttopright、以及bottom属性进行规定fixed

生成绝对定位的元素,相对于浏览器窗口进行定位,元素的位置通过lefttopright以及bottom属性进行规定relative

生成相对定位的元素,相对于其正常位置进行定位,因此,left:20会向元素的LEFT位置添加20像素static

默认值,没有定位,元素出现正常的流中(忽略topbottomleftright或者z-index声明)inherit

规定应该从父元素继承position属性的值

11、css中哪些属性可以继承( )

A、font-size

B、color

C、font-family

D、border

解析:

ABC

margin padding border display 不可以继承

12、cssclear的作用是什么?( )

A、清除该元素所有样式

B、清除该元素父元素的所有样式

C、指明该元素周围不可出现浮动元素

D、指明该元素的父元素周围不可出现浮动元素

解析:

C

clear` : `none` | `left` | `right` | `both

(推荐教程:CSS教程

对于CSS的清除浮动(clear), 这个规则只能影响使用清除的元素本身,不能影响其他元素

文章来源:公众号–前端人 作者:鬼哥

以上就是W3Cschool编程狮关于12题精选的CSS面试题(含解析)的相关介绍了,希望对大家有所帮助。

做JS逆向时,Python怎么调用JS代码

thbcm阅读(182)

有次在做JS逆向时,我不知道怎么用Python去实现,估计是我水平不够,那该怎么办呢?本文给大家介绍一个第三方库——pyexecjs,它能很好的解决我碰到的问题,可以用python运行JavaScript代码。

配置环境

node.js

下载地址:https://nodejs.org/en/download/

选好版本直接下就好了,它会自动加入系统环境的,检查Node.js版本,出现版本号就说明配置好了。

Pyexecjs

pip install pyexecjs

基本使用

先检查一下使用的引擎是否为node.js

import execjs


print(execjs.get().name)
运行结果:
Node.js (V8)

运行js代码

pyexecjs运行js代码有两种方法

一、eval()

eval()可以直接执行js代码

import execjs


print(execjs.eval("a = new Array(1, 2, 3)"))
运行结果:
[1, 2, 3]

二、compile()

代码量多的话就推荐用这个方法,先将js代码写入一个文件中,需要的时候读取执行即可。

创建js_text.js文件,写入如下代码:

function a(str) {
    return str;
}

python代码:

import execjs


with open('js_text.js', 'r', encoding='utf-8') as f:
    jstext = f.read()


ctx = execjs.compile(jstext)
a = '123456'
result = ctx.call('a', a)
print(result)
运行结果:
123456

先调用compile编译js代码,再调用call方法进行执行,call的第一个参数的js代码中的函数名,第二个参数是该函数需要的参数(如果有多个参数,直接逗号写下一个参数即可)。

常见的问题

js代码返回的字符串如果有特殊字符的话可能会出错。

解决方法就是先将字符串进行 base64 编码后再返回。

function a(str) {
    return new Buffer(str).toString("base64");
}

有了这个方法你就可以不用重写代码了,直接扣js代码运行即可,扣代码有时会有些变量未声明,在js代码中查找补全即可,帮助你节省脑力。

文章来源:www.toutiao.com/a6864498293032878604/

以上就是W3Cschool编程狮关于做JS逆向时,Python怎么调用JS代码的相关介绍了,希望对大家有所帮助。

完整的Explain宝典,SQL优化再也不用担心了

thbcm阅读(199)

在日常编程中,我们需要经常与数据库打交道,所以本文给大家带来一些关于Explain的总结,让大家更好的处理SQL

select 语句之前增加 explain 关键字,MySQL 会在查询上设置一个标记,执行查询时,会返回执行计划的信息,而不是执行这条SQL(如果 from 中包含子查询,仍会执行该子查询,将结果放入临时表中)

CREATE TABLE `film` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;




CREATE TABLE `actor` (
  `id` int(11) NOT NULL,
  `name` varchar(45) DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;




CREATE TABLE `film_actor` (
  `id` int(11) NOT NULL,
  `film_id` int(11) NOT NULL,
  `actor_id` int(11) NOT NULL,
  `remark` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_film_actor_id` (`film_id`,`actor_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

两个变种

explain extended

会在 explain 的基础上额外提供一些查询优化的信息。紧随其后通过 show warnings 命令可以 得到优化后的查询语句,从而看出优化器优化了什么。额外还有 filtered 列,是一个半分比的值,rows * filtered/100 可以估算出将要和 explain 中前一个表进行连接的行数(前一个表指 explain 中的 id 值比当前表 id 值小的表)

mysql> explain extended select * from film where id = 

mysql> show warnings;

explain partitions

相比 explain 多了个 partitions 字段,如果查询是基于分区表的话,会显示查询将访问的分区。

(推荐课程:SQL教程)

id列

id列的编号是 select 的序列号,有几个 select 就有几个id,并且id的顺序是按 select 出现的顺序增长的。 MySQLselect 查询分为简单查询(SIMPLE)和复杂查询(PRIMARY)。 复杂查询分为三类:简单子查询、派生表(from语句中的子查询)、union 查询。 id列越大执行优先级越高,id相同则从上往下执行,idNULL最后执行**

select_type列

select_type 表示对应行是简单还是复杂的查询,如果是复杂的查询,又是上述三种复杂查询中的哪一种。

1)simple 简单查询:查询不包含子查询和union

mysql> explain select * from film where id = 2;

2)primary:复杂查询中最外层的 select

3)subquery:包含在 select 中的子查询(不在 from 子句中)

4)derived:包含在 from子句中的子查询。MySQL会将结果存放在一个临时表中,也称为派生表(derived的英文含义) 用这个例子来了解 primarysubqueryderived 类型

mysql> explain select (select 1 from actor where id = 1) from (select * from film where id = 1) der;

5)union:在 union 中的第二个和随后的 select

6)union result:从 union 临时表检索结果的 select 用这个例子来了解 unionunion result 类型:

mysql> explain select 1 union all select 1;

table列

这一列表示 explain 的一行正在访问哪个表。 当 from 子句中有子查询时,table列是 格式,表示当前查询依赖 id=N 的查询,于是先执行 id=N 的查询。 当有 union 时,UNION RESULTtable 列的值为<union1,2>,1 和 2 表示参与 unionselect 行 id 。

type列

这一列表示关联类型或访问类型,即MySQL决定如何查找表中的行,查找数据行记录的大概范围。 依次从最优到最差分别为:system > const > eq_ref > ref > range > index > ALL 一般来说,得保证查询达到range级别,最好达到ref

NULL

mysql能够在优化阶段分解查询语句,在执行阶段用不着再访问表或索引。例如:在索引列中选取最小值,可以单独查找索引来完成,不需要在执行时访问表

mysql> explain select min(id) from film;

const, system

mysql能对查询的某部分进行优化并将其转化成一个常量(可以看show warnings 的结果)。用于 primary keyunique key 的所有列与常数比较时,所以表最多有一个匹配行,读取 1 次,速度比较快。systemconst的特例,表里只有一条元组匹配时为system

mysql> explain extended select * from (select * from film where id = 1) tmp;

eq_ref

primary keyunique key 索引的所有部分被连接使用 ,最多只会返回一条符合条件的记录。这可能是在 const 之外最好的联接类型了,简单的 select 查询不会出现这种 type

mysql> explain select * from film_actor left join film on film_actor.film_id = film.id;

ref

相比 eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前缀,索引要和某个值相比较,可能会找到多个符合条件的行。

1.简单 select 查询,name是普通索引(非唯一索引)

mysql> explain select * from film where name = "film1";

2.关联表查询,idx_film_actor_idfilm_idactor_id的联合索引,这里使用到了film_actor的左边前缀film_id部分。

mysql> explain select film_id from film left join film_actor on film.id = film_actor.film_id;

range

范围扫描通常出现在 in(), between ,> ,<, >=等操作中。使用一个索引来检索给定范围的行。

mysql> explain select * from actor where id > 1;

index

扫描全表索引,这通常比ALL快一些。( index 是从索引中读取的,而 all 是从硬盘中读取)

mysql> explain select * from film;

ALL

即全表扫描,意味着mysql需要从头到尾去查找所需要的行。通常情况下这需要增加索引来进行优化了

mysql> explain select * from actor;

(推荐课程:MySQL教程)

possible_keys列

这一列显示查询可能使用哪些索引来查找。 explain 时可能出现 possible_keys 有列,而 key 显示 NULL 的情况,这种情况是因为表中数据不多,mysql认为索引对此查询帮助不大,选择了全表查询。 如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查 where 子句看是否可以创造一个适当的索引来提高查询性能,然后用 explain 查看效果。

key列

这一列显示mysql实际采用哪个索引来优化对该表的访问。 如果没有使用索引,则该列是 NULL。如果想强制mysql使用或忽视possible_keys列中的索引,在查询中使用 force indexignore index

key_len列

这一列显示了mysql在索引里使用的字节数,通过这个值可以算出具体使用了索引中的哪些列。 举例来说,film_actor的联合索引 idx_film_actor_idfilm_idactor_id两个int列组成,并且每个int是 4字节。通过结果中的key_len=4可推断出查询使用了第一个列:film_id列来执行索引查找。

mysql> explain select * from film_actor where film_id = 2;

“key_len计算规则如下: 字符串 char(n):n字节长度 varchar(n):2字节存储字符串长度,如果是utf-8,则长度 3n + 2 数值类型 tinyint:1字节 smallint:2字节 int:4字节 bigint:8字节   时间类型  date:3字节 timestamp:4字节 datetime:8字节”

如果字段允许为 NULL,需要 1字节记录是否为 NULL 索引最大长度是 768字节,当字符串过长时,mysql会做一个类似左前缀索引的处理,将前半部分的字符提取出来做索引。

ref列

这一列显示了在key列记录的索引中,表查找值所用到的列或常量,常见的有:const(常量),字段名(例:film.id)

rows列

这一列是mysql估计要读取并检测的行数,注意这个不是结果集里的行数。

Extra列

这一列展示的是额外信息。常见的重要值如下:

Using index

查询的列被索引覆盖,并且where筛选条件是索引的前导列,是性能高的表现。一般是使用了覆盖索引(索引包含了所有查询的字段)。对于innodb来说,如果是辅助索引性能会有不少提高

mysql> explain select film_id from film_actor where film_id = 1;

Using where

查询的列未被索引覆盖,where筛选条件非索引的前导列

mysql> explain select * from actor where name = 'a';

Using where Using index

查询的列被索引覆盖,并且where筛选条件是索引列之一但是不是索引的前导列,意味着无法直接通过索引查找来查询到符合条件的数据

mysql> explain select film_id from film_actor where actor_id = 1;

NULL

查询的列未被索引覆盖,并且where筛选条件是索引的前导列,意味着用到了索引,但是部分字段未被索引覆盖,必须通过“回表”来实现,不是纯粹地用到了索引,也不是完全没用到索引

mysql>explain select * from film_actor where film_id = 1;

Using index condition

Using where类似,查询的列不完全被索引覆盖,where条件中是一个前导列的范围;

mysql> explain select * from film_actor where film_id > 1;

Using temporary

mysql需要创建一张临时表来处理查询。出现这种情况一般是要进行优化的,首先是想到用索引来优化。

1.actor.name没有索引,此时创建了张临时表来distinct

mysql> explain select distinct name from actor;

2.film.name建立了idx_name索引,此时查询时extrausing index,没有用临时表

mysql> explain select distinct name from film;

(推荐微课:MySQL微课)

Using filesort

mysql会对结果使用一个外部索引排序,而不是按索引次序从表里读取行。此时mysql会根据联接类型浏览所有符合条件的记录,并保存排序关键字和行指针,然后排序关键字并按顺序检索行信息。这种情况下一般也是要考虑使用索引来优化的。

1.actor.name未创建索引,会浏览actor整个表,保存排序关键字name和对应的id,然后排序name并检索行记录

mysql> explain select * from actor order by name;

2.film.name建立了idx_name索引,此时查询时extrausing index

mysql> explain select * from film order by name;

文章来源:juejin.im/post/6863832433062739981

以上就是W3Cschool编程狮关于完整的Explain宝典,SQL优化再也不用担心了的相关介绍了,希望对大家有所帮助。

联系我们