标签: mysql

  • 从40秒到11毫秒:TiDB Cloud一次SQL深潜优化实战

    从40秒到11毫秒:TiDB Cloud一次SQL深潜优化实战

    此文参加了 TiDB 社区第四届专栏征文大赛,获得二等奖。感谢 TiDB,感谢 Gemini,感谢跟我一起做 Awesome Comment 的某同学。

    有空的同学麻烦帮我投个票:https://asktug.com/t/topic/1046966 搜索“深潜”就能找到给我投票的地方

    在数据库应用中,慢SQL是常见的性能瓶颈。本文将详细记录一次针对TiDB Cloud v7.5.2环境中复杂评论查询的SQL优化过程,如何通过分析执行计划、添加索引、改写SQL(使用EXISTSUNION)等手段,将一个40多秒的查询逐步优化到11毫秒,希望能为读者提供有价值的实战参考。

    不知道什么时候,TiDB Cloud 升级到 v7.5.2,于是我们的评论应用 RU 消耗开始起飞,达到以往月份的 3 倍左右。没办法,只好拖着病榻之躯来 Debug。还好 Gemini 2.5 Pro 给力,很快我就完成了这次优化,记录在这篇博客里。另外,这篇博客也是 Gemini 2.5 Pro 帮我写的,AI 之力,恐怖如斯 😱😱。

    (更多…)
  • 记一次不成功的数据库搬家

    记一次不成功的数据库搬家

    我这个博客始建于 2011 年,当时我从 ZOL 离职,表达欲旺盛的我,希望找个地方继续写博客,于是就买了台集成 WordPress 的虚拟主机,开始写技术博客。除博客之外,我也利用这个平台学习 Linux、nginx、PHP、MySQL 等服务器端软件的使用,从中受益良多。

    后来忘记哪年,从虚拟主机换到 VPS,终于可以拥有自己全权控制的服务器了。当时很兴奋,觉得自己应该在后端取得更深入的发展,于是开始研究 PHP + WordPress 开发,准备基于 WordPress 做一些尝试,模版、插件、WooCommerce,感觉能做的东西很多。

    不过坦率地说,WordPress 历史太久,历史包袱太多,代码架构不好,二次开发很难受,属于能用但是不好用的状态,所以我的学习进度一直不快。另一方面,我的职业机会也不少,所以真正投入在这个方向的时间并不多,拖着拖着就黄了。

    不过我一直在坚持更新博客,后来我发现数据库也不小,每次导出 + 备份都需要不少时间。正巧当时我了解到 TiDB,还恰逢 TiDB Cloud 初次发布,感觉似乎可以把数据库放在云端,这样就一下解决了自动扩容和自动备份这两个需求(其实都没需要)。于是就把数据库迁移过去。

    最初使用 TiDB Cloud 时,因为刚刚参加完 TiDB Hackathon,账户上有免费赠送的额度,所以没感觉有什么问题。但是从去年开始,我发现每个月都要付给 TiDB 几美金费用。我研究了一下 TiDB Cloud 的计费体系,主要分成两大项:

    1. 容量。免费 5G,我的用量远远不够。
    2. 计算用量,RU。不太确定怎么计算,但是我用超甚多,所以费用都产生自这里。

    我首先排除 Slow SQL 的问题。WordPress 这点做得挺好,毕竟是世界上用户数量最大的软件,我也没有用很多来源不明的插件,所以并没有 Slow SQL。

    接下来通过 Metrics 面板查阅,看不太出来用量和时间有什么关系,偶尔我的博客访问量大的时候,RU 会涨一点点,但其实影响不大。

    然后我试图增加 CDN,给页面增加缓存时间等,意图通过降低访问压力降低数据库消耗,但也都没什么实际作用。

    然后我尝试移除一些插件,比如 Jetpack 系列,避免来自 WordPress 官方的请求消耗掉我的额度,也失败了。

    最后我只好诉诸代码,研究来研究去,得到结论:

    1. 我这个博客时间很久,收录也不错,多半已经处于各种垃圾营销的大列表里面
    2. 于是会有很多自动化的机器人来抓取、注册、灌垃圾评论等等
    3. 这些机器人大部分不走网页,没法被缓存,而且每次都会大量读写 wp_options
    4. 不知道具体原因,不过对于 TiDB Cloud 来说,这种对小表的高频请求会消耗大量的 RU
    5. 在不改变架构的前提下,这个问题无法解决

    于是,在九月份被刷掉 $15 之后,我终于忍不了了,利用国庆,把数据库迁回本地。随便刷吧,nnd。下一步,肯定是要重新建站了,把 WordPress 当成纯 C MS,前端用现代化架构重新构建。正好也督促自己把 Awesome Comment 的 SaaS 做起来。

    最后强调一下,这个跟 TiDB Cloud 没关系,纯粹因为项目不合适。我现在有好几个服务都在蹭他们家的免费流量,一点问题没有,非常顺利。

  • 💪 WordPress 使用 TiDB Cloud 替换 MySQL 💪

    💪 WordPress 使用 TiDB Cloud 替换 MySQL 💪

    白嫖使我快乐。一直白嫖,一直快乐,😊。感谢 TiDB,感谢 TiDB Cloud,你们让我的博客内容更丰富多彩。

    前言

    我这个博客从 2011 年开始写,如今已经 12 年。最早,从 ZOL 离职后,我需要换个新平台写博客;另一方面,我也想学习 Linux、PHP、MySQL,这些原本不熟悉的技术,于是选择了 WordPress。这么多年来,服务器从共享主机搬迁到 VPS,又升级到云主机;PHP 从 5.5、5.6 升级到 7,又升级到 8;Apache 被换成 Nginx;唯独数据库没有变化,基本一套老框架沿用至今。

    我前阵子发现:因为编码问题,无法在标题里或正文里插入表情符号。于是升级数据库也被提上日程。刚好我一直关注的 TiDB 开始提供云数据库服务,采用 Serverless 模式,Free Tier 有 5GB 可以用,足够我写博客。于是我就准备趁此机会切换到 TiDB Cloud 上。

    注册 TiDB Cloud

    打开 tidbcloud.com 注册即可。

    TiDB Cloud 很大方,不需要绑卡,不需要繁琐的操作,直接第三方登录就可以使用。Serverless(Free Tier)只支持 AWS 机房,可选的位置也不多,因为我 ECS 买在美西,所以就把数据库也买在美西,这样速度应该会快一些。

    每个账号可以创建若五个 Serverless 节点,有免费额度,超过限制用量则开始收费。只要稍稍点两下,就建好了,体验很流畅,这里不再赘述。接下来开始使用。

    连接 TiDB Cloud

    数据库准备就绪之后,我们可以进入数据库详情页。点击右上角的“Connect”按钮,即可打开连接信息窗口。

    TiDB 贴心的准备了各种客户端、各种平台的连接方式,对于我这种数据库准小白来说非常有帮助。第一次使用,需要点击右下角的“Reset password”按钮生成数据库密码,生成后,这个按钮会变成复制密码。记得要妥善保存密码,因为我们不能再次查看或者获取。

    这里发现一个设计缺陷:点击 Reset password 按钮没有确认过程,所以我的数据库密码直接就被改掉了……这种破坏性操作还是应该多一个确认比较好,回头反馈给 TiDB 的工作人员。

    导入数据

    TiDB Cloud 只支持导入 CSV 文件,比较难用。可能因为我数据库知识储备不足,我甚至想象不出 CSV 该怎么支持表结构😅。不过没关系,我有 JetBrains 全家桶,命令行操作也还凑合,所以直接从本地跑就行。

    首先在服务器上执行 mysqldump -u USER -p --database BLOG_DB > backup.sql 把整个博客数据库包含数据结构都导出到 backup.sql 文件里。

    打开 sql 文件,把数据库的编码全部改成 utf8mb4 或者 utf8mb4_unicode_ci,这样就可以支持表情符号咯 🎉🎉。

    打开 DataGrip,按照上一节介绍建立数据库连接,右键,选择“SQL Scripts”,然后执行刚才的 SQL,等一会儿,数据即可完成导入。当然,实际过程肯定没有这么顺利,不过云数据库嘛,有问题就直接整个节点干掉再重建就好。不留脏数据。

    WordPress 连接

    TiDB Cloud 要求必须使用 TLS 安全连接,所以我们需要修改 wp-config.php

    /** WordPress数据库的名称 */
    define('DB_NAME', 'blog');
    
    /** MySQL数据库用户名 */
    define('DB_USER', '用户名');
    
    /** MySQL数据库密码 */
    define('DB_PASSWORD', '密码');
    
    /** MySQL主机 */
    define('DB_HOST', '主机域名:端口');
    
    /** 使用 SSL 连接 */
    define('MYSQL_CLIENT_FLAGS', MYSQLI_CLIENT_SSL);
    

    因为我的服务器是 Ubuntu 22.04 系统,证书放在默认位置,可以自动加载。如果你使用其它系统,可能需要修改一下证书路径。

    解决 SQL_CALC_FOUND_ROWS 导致翻页丢失的问题

    完成上述操作,打开博客,500 😱。查看 error.log,原来是不支持 SQL_CALC_FOUND_ROWS 导致报错。我并不知道这个东西是干嘛用的,丢到 Google 里搜索,找到这个 issue,原来 WordPress 的 SQL 要使用这个函数,但是 TiDB 并不支持。

    解决方案是在数据库里执行 SET GLOBAL tidb_enable_noop_functions=1。之后 WordPress 不会再报错,但是相应的,翻页功能也没有了,因为 WordPress 无法统计博文数量。

    继续搜索。看起来 SQL_CALC_FOUND_ROWS 并不是什么好东西,不知道为什么 WordPress 至今都不愿意把它移除。还好,WP 留有开关,我们可以关闭这个函数的使用。我自己创建了一个 WP 小插件,用来给博客加广告、调整页面,所以我就在里面添加函数,关掉 SQL_CALC_FOUND_ROWS

    if ( ! function_exists( 'wpartisan_set_no_found_rows' ) ) :
      function wpartisan_set_no_found_rows( \WP_Query $wp_query ) {
        $wp_query->set( 'no_found_rows', true );
      }
    endif;
    add_filter( 'pre_get_posts', 'wpartisan_set_no_found_rows', 10, 1 );
    
    if ( ! function_exists( 'wpartisan_set_found_posts' ) ) :
      function wpartisan_set_found_posts( $clauses, \WP_Query $wp_query ) {
        // Don't proceed if it's a singular page.
        if ( $wp_query->is_singular()  ) {
          return $clauses;
        }
    
        global $wpdb;
    
        // Check if they're set.
        $where = isset( $clauses[ 'where' ] ) ? $clauses[ 'where' ] : '';
        $join = isset( $clauses[ 'join' ] ) ? $clauses[ 'join' ] : '';
        $distinct = isset( $clauses[ 'distinct' ] ) ? $clauses[ 'distinct' ] : '';
    
        // Construct and run the query. Set the result as the 'found_posts'
        // param on the main query we want to run.
        $wp_query->found_posts = $wpdb->get_var( "SELECT $distinct COUNT(*) FROM {$wpdb->posts} $join WHERE 1=1 $where" );
    
        // Work out how many posts per page there should be.
        $posts_per_page = ( ! empty( $wp_query->query_vars['posts_per_page'] ) ? absint( $wp_query->query_vars['posts_per_page'] ) : absint( get_option( 'posts_per_page' ) ) );
    
        // Set the max_num_pages.
        $wp_query->max_num_pages = ceil( $wp_query->found_posts / $posts_per_page );
    
        // Return the $clauses so the main query can run.
        return $clauses;
      }
    endif;
    add_filter( 'posts_clauses', 'wpartisan_set_found_posts', 10, 2 );

    调整完毕,翻页功能恢复正常。

    使用体验小结

    Serverless 讲究一个即用即起,平时是休眠状态,有人用才会启动。所以最好避免冷启动,否则第一个用的人要等很长时间,体验很差。作为博客,更简单的方式是配置 CDN,给首页外的页面添加长期缓存,就能改善大部分用户的体验。

    实际上我目前使用了一个多月,并没有冷启动的感觉。我的博客日均几百的访问量,感觉相当可以。另外一个实例,作为 NoCoDB 的数据库,因为访问量很小,所以冷启动感觉很明显。

    其它方面,速度和效率都不比本地数据库差,以后我再搞产品,应该都不会自己数据库了,嘿嘿嘿。


    后记

    我已经两次报名参加 TiDB Hackathon,第一次止步外围筛选第二次虽然进入决赛圈,但是因为对云数据库不了解,挑战开发 NoCoDB+TiDB Cloud 失败,铩羽而归。

    但是借用群里同学的说法:人生没有白走的路,每一步,都算数。虽然 Hackathon 没能获得好成绩,但是我获得了更多关于 TiDB Cloud 的知识,于是这次可以顺利完成迁移。

    目前博客网站使用体验良好,速度不比之前使用本地数据库慢。前两天,我发现 TiDB Cloud 开放了 Data Service,提供 HTTP Endpoint,这表示着他们向 DaaS(Database as a Service)更近一步,也意味着我们在 Edge Function 里使用 TiDB Cloud 成为可能。那么接下来,我打算好好利用一下这个薅羊毛机会,把几个 Side Project 的数据端放在 TiDB Cloud 上,跟 Supabase 比一比,看哪个更好用,更适合新项目、小公司从零到一。

    到时候也会写成更多文章,或许做成视频,跟大家分享。

    如果各位读者老爷对数据库、云开发、薅羊毛感兴趣,欢迎留言讨论,可提出问题,亦可指点一二,均非常欢迎。

  • 重置 MariaDB root 密码

    重置 MariaDB root 密码

    以我最常用的 Ubuntu 为例,记录如何重置 MariaDB root 密码。MariaDB 是 MySQL 原作者在 MySQL 闭源之后的再起之作,基本能完全兼容 MySQL。在我的 Ubuntu 上,它的大部分命名都沿用 MySQL。

    1. 停掉 mysql 服务

    $ service mysql stop

    2. 以特殊方式启动 mysql

    使用 --skip-grant-tables 可以跳过用户权限检查,让你没有密码也能连接。此时为了避免安全问题,可以加上 --skip-networking,防止有人此时通过网络连接你的数据库。在本地环境下差别不大。

    $ sudo mysqld_safe --skip-grant-tables --skip-networking &

    这一步可能会启动失败,导致下一步连接时报错:ERROR 2002 (HY000): Can’t connect to local MySQL server through socket ’/var/run/mysqld/mysqld.sock’ (2),此时,可以这样修正:

    $ sudo mkdir /var/run/mysqld
    $ sudo chown mysql /var/run/mysqld

    3. 连接数据库

    $ mysql -u root

    正常情况下就连上了。

    4. 重置密码

    mysql> FLUSH PRIVILEGES;
    mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'new_password';

    5. 重启 mysql

    按照参考文档里介绍的做法,需要

    $ sudo kill `cat /var/run/mysqld/mysqld.pid`
    # 或
    $ sudo kill `/var/run/mariadb/mariadb.pid`

    不过我实际操作的时候只要把(2)开启的进程关掉就好了。然后重启 mysql 服务:

    $ service mysql restart

    6. 完成

    尝试一下连接,应该可以成功了:

    $ mysql -uroot -p

    7. Ghost 连接

    虽然命令行连接成功,但是 ghost start 仍然报错,经查,是 Ghost 的数据库连接方式有点问题,修改 config.production.json,加入一行配置即可,大体如下:

    {
      "client": "mysql",
      "connection": {
        ....
        "socketPath": "/var/run/mysqld/mysqld.sock",
      }
    }
    (更多…)
  • 解决 PHP 7.2.8 + MySQL 8.0.12 连接失败的问题

    解决 PHP 7.2.8 + MySQL 8.0.12 连接失败的问题

    这两天又反复遇到这个问题,先写解决方案:

    1. 使用 caching_sha2_password  插件

    修改用户密码,并且用插件生成,可以解决 WordPress 的问题。

    ALTER USER 'user'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'password';

    2. 修改 my.cnf,使用原生密码

    使用 Laravel + MySQL 8.0 的时候,遇到

    SQLSTATE[HY000] [2054] The server requested authentication method unknown to the client

    修改 /etc/mysql/my.cnf (我是 Ubuntu 16.04),添加下面一行:

    [mysqld]
    default_authentication_plugin= mysql_native_password

    然后重置密码:

    ALTER USER 'user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';
    (更多…)
  • WordPress + MySQL 8

    如果遇到验证数据库链接失败的问题,可以这样:

    ALTER USER 'username'@'localhost' identified with mysql_native_password by 'password';
    
  • MySQL 的编码问题

    MySQL 的编码问题

    前几天朋友的小程序遇到点问题,同样的代码,有些人就是注册不上,有些人就没问题。让我帮忙看。

    看代码应该没问题,我自己试也没问题,很诡异。后来我发现,说注册不上的一个截图里,昵称里有一个雨滴的符号。我们知道,传统的编码是没有表情符号的,表情符号是 Unicode 后期才加进去的,那么会不会是数据库字段的问题?看了一眼,Laravel 默认创建的数据库,字符类型是 utf8mb4_unicode_ci,而他们数据库是 utf8_general_ci,Google 一下,找到下面这篇文章:

    為什麼MYSQL要設定用UTF8MB4編碼 UTF8MB4_UNICODE_CI

    里面提到:

    當資料庫需要儲存或處理以下資料:emoji (手機端常用的表情字符)

    应该使用 utf8mb4_unicode_ci,因为它会用更多的空间存储字符。基本锁定是字符集的问题。然后看到梦康大的一篇博文:直接使用 mysql utf8 存储 超过三个字节的 emoji 表情 ( 不使用 utf8mb4 ),决定参考他的方案,毕竟改库改表不是小事情。

    不过时过境迁,梦康文中的 func_overload 已经可以用 mb_strlen($str, '8bit') 来替代,所以最后的代码大约是这样的:

    // 替换
    protected function encodeEmoji($input) {
      $length = mb_strlen($input, 'utf-8');
      $result = '';
    
      for ($i = 0; $i < $length; $i++) {
        $tmp = mb_substr($input, $i, 1, 'utf-8');
        if (mb_strlen($tmp, '8bit') >= 4) {
          $result .= '[[Emoji:' . rawurlencode($tmp) . ']]';
        } else {
          $result .= $tmp;
        }
      }
      return $result;
    }
    
    // 替换回
    protected function decodeEmoji($nickname) {
      return preg_replace_callback('~\[\[Emoji:(.*?)\]\]~', function ($matches) {
        return rawurldecode($matches[1]);
      }, $nickname);
    }
    

    所以说,程序员心态要保持年轻,步调要跟年轻人保持一致,这样才更容易发现新问题,所以我的昵称已经改成“肉山🎩”了。

  • Ubuntu 16.04 搭建 LNMP 开发环境

    Ubuntu 16.04 搭建 LNMP 开发环境

    前天帮人配了台机器,未来可能还要帮人配。在学会用 Docker 之前,先写一篇记录下怎么搭建环境吧。

    这篇收费!¥4.99,请阅后自觉打钱。

    (更多…)
  • 使用 Windows Linux Subsystem 配置开发环境

    使用 Windows Linux Subsystem 配置开发环境

    微软前些日子公布了 Windows Subsystem Linux(下简称WSL),当时还只有 Insider Preview 支持安装,作为普通用户的我就没有尝试。这两天不知道伴随哪次更新,标准的专业版也可以安装了,就试着配了一下,发现效果还比较理想,基本可以替代之前的 IIS 或者 VirtualBox 方案。这里简单记录一下。

    (2016-12-02 更新)随着最近一次 1607 更新,家庭版也可以安装了,效果基本一样。
    (2018-01-21 更新)已知 Yarn 在非 ~/ 开头的目录里 install 会出问题

    安装 WSL

    开始 > 设置 > 系统 > 应用和功能 > 拖到最下面“程序和功能”。然后点击“启用或关闭 Windows 功能”,找到“适用于 Linux 的 Windows 子系统(beta)”,启动之。

    启用 Windows Subsystem Linux

    之后系统会要求重启,听它的。

    然后打开命令行(或者 PowerShell,下同),输入 bash,提示需要切换到开发者模式。按照提示切过去,然后再次运行 bash,系统会安装需要的组件。这个过程会比较久,大概源都在国外吧。安装完成后又要重启,之后 WSL 就算安装成功。

    再次打开命令行,输入 bash,就可以进入 Linux 子系统了。

    WSL 其实就是 Ubuntu 14.04.5,所以安装包直接用 apt-get 就可以。嫌慢的话可以换上国内的源,不赶时间就让它慢慢更新。

    sudo apt-get update
    sudo apt-get upgrade

    配置Apache

    【2020-04-06 更新】现在已经没这个问题了,建议直接删掉 apache。

    这次我本来打算用 Nginx,装上之后启动不了,Google 之,似乎是 WSL 对 IP v6 的支持有问题,遂卸掉继续用 Apache。反正本地开发嘛,性能并发不是什么问题。

    Ubuntu 自带 Apache 2.4.7,直接 sudo service apache2 start 启动就好。不过这里遭遇到 vhost 的问题,我配置的虚机怎么都不生效,最后在 stack overflow 上找到解决方案,说是 000-default 和自定义的主机冲突,禁用掉默认配置就好了。

    sudo a2enmod vhost_alias
    sudo a2dissite 000-default
    sudo a2ensite 001-mysite

    然后还需要调整一下模块,比如要进行 WordPress 开发,就要保证重定向生效。

    sudo a2enmod php7.1
    sudo a2enmod rewrite
    

    另外,Apache 2.4 要求配置目录的“访问条件”,不然不能访问(会返回 “403 Forbidden”,改目录权限也没有用),所以要增加一行:

    <Directory "/path/to/my/site">
      ...
      Require all granted # 允许所有来源
      AllowOverride All # 允许重定向,WordPress 必备
      ...
    </Directory>

    全部配置好之后,重启 apache,访问配置的域名,就可以了。

    安装 Nginx

    不知道什么时候 Nginx 可以用了,反正我某天试着配了一下,突然就可以用了。

    sudo apt install nginx

    安装完配置一下即可,不过需要注意,apache 默认启动并占据 80 端口,所以使用 nginx 前必须卸载或者关掉 Apache,避免出现端口冲突。

    安装 PHP

    添加 PHP 源。

    sudo add-apt-repository ppa:ondrej/php

    需要注意,以前那个 ppa:ondrej/php5-5.6 的源已经被移除了,5.5 到 7.1 都用 ondrej/php 这个新源。如果非要用 5.4 的话,还有个 php5-oldstable 的源。

    这里顺便把几个可能用到的组件也装上。

    sudo apt-get install php
    sudo apt-get install php-mysql
    sudo apt-get install php-xdebug
    sudo apt-get install php-apcu
    sudo apt-get install php-xml
    sudo apt-get install php-mcrypt

    配置 Xdebug

    配置好 PHP 之后,开启 Xdebug 并不复杂。以我安装的 PHP7.1为例,安装之后,配置文件都在 /etc/php/7.1/apache2/conf.d/ 这个文件夹里,找到 20-xdebug.ini 并添加以下几行,然后重启 apache,即可。

    xdebug.remote_enable=on
    xdebug.remote_autostart=on
    xdebug.idekey=PHPSTORM

    使用 cmder 替换 PowerShell

    PowerShell 也好,自带的命令行也好,都不好用。要么字体难看得要死,要么中文输入有问题,而且不支持多窗口。这里推荐 cmder,很好用,体验提升明显。

    cmder 支持打开窗口并执行启动脚本,可以一键进入 Linux 控制台,只要按下图进行配置即可。

    配置 cmder 支持 WSL

    然后再修改启动时默认的 tab。

    修改 cmder 启动时默认打开的 tab 类型

    启用 Ruby

    这个版本的 Ubuntu 集成了 rvm,通过 rvm list 可以看到已经安装了 Ruby 2.3.0。但是直接运行 ruby 却提示没有安装,运行 rvm use 2.3.0 提示

    RVM is not a function, selecting rubies with ‘rvm use …’ will not work.
    You need to change your terminal settings to allow shell login.
    Please visit https://rvm.io/workflow/screen/ for example.

    这个很奇怪,以前没有遇见过。通过 Google 得知,这是因为默认的登录方式没有读取 ~/.bash_login 里的配置信息,没有加载到 rvm 需要的方法,所以需要在启动 WSL 的时候增加参数 --login,所以回到上一步,修改启动脚本,问题解决。

    %windir%\system32\bash.exe ~ -cur_console:p --login

    (这里我还去掉了zsh,因为这台电脑似乎没有安装这个包,我不知道是不是默认不安装的问题。)

    安装 MySQL

    【2017-03-25 更新】下面提到的坑跟我用“旧”配置——0.6.0 有关。这个配置参考自 digital ocean 的文章,实际上 Oracle 已经推出了新版本的配置文件,使用新版配置文件安装 MySQL 就不会遇到这个问题。新版配置文件的链接在此。其它步骤相同。

    原以为安装 MySQL 最简单,没想到也有坑……主要是 5.7 的坑。

    我比较喜欢新版本,所以自然就准备上 5.7。Ubuntu 自带的源是 5.5 或者 5.6,所以要先更新一下:

    wget http://dev.mysql.com/get/mysql-apt-config_0.6.0-1_all.deb
    sudo dpkg -i mysql-apt-config_0.6.0-1_all.deb
    sudo apt-get update

    好了,接下来直接安装就好:

    sudo apt-get install mysql-server

    根据提示信息安装 MySQL Server 5.7,设置 root 密码。这个阶段多半不会出问题。安装完成之后,运行 mysql -uroot -p,输入刚才设置的密码,不出意外的话就可以进入 MySQL 控制台了。

    进入 MySQL 控制台

    接下来我们通常要设置开发用户,此时问题也就来了。(这个问题在我的几台机器上,包括 VPS 表现不一,时有时无,所以我不太确定到底什么情况下会发生。)

    CREATE USER 'someone'@'localhost' IDENTIFIED BY 'password';

    执行这句 SQL 将创建一个用户,看起来完全不会有问题,结果却报错了:ERROR 1054 (42S22): Unknown column 'plugin' in 'mysql.user'(报错的列可能不同,但原因是一样的)。Google 之,原来随着 MySQL 版本升级,系统内建表的结构也发生着变化,5.7 需要的字段比起 5.5 甚至 5.4 多了不少。虽然我们直接安装的 5.7,也不耽误它按照老的结构创建表……

    这个时候有两个选择,一是手工把差的列加上,另一个则是运行官方提供的升级脚本 mysql_upgrade。为表达对官方的信任,我选择了后者。退出 MySQL,回到 SSH,执行脚本:

    mysql_upgrade --upgrade-system-tables -u root -p

    升级完成之后,再 mysql -uroot -p,输入密码,登录不进去了!WTF!怎么回事?!

    又是一通 Google,原来 5.7 之后,开发团队移除了 mysql.user 表里的 password 字段,用 authentication_string 取而代之,然而这个字段并没有复制之前 password 的值,所以原来的密码就失效了!这是什么鬼……

    这里我们又有两个选择,一是想办法修改 root 密码,二是卸了重装……因为我当时没找到真正的原因(就是上面这个),所以选择了后者。现在我推荐一个解决方案:

    1. 安装后,进入 MySQL 控制台,输入 DESC mysql.user;
    2. 如果返回 45 rows(行),则不需要升级系统内建表,改干嘛干嘛;如果少于这个数,就要执行 mysql_upgrade
    3. 刚才的窗口不要动,新开一个窗口,执行 mysql_upgrade --upgrade-system-tables -u root -p
    4. 执行完毕回到刚才的窗口,在 MySQL 环境下执行
    UPDATE `mysql`.`user`
    SET `authentication_string`=PASSWORD('password')
    WHERE `user`='root';

    文件互访

    在 WSL 里访问系统中的文件比较容易,使用 /mnt/盘符/路径 即可。

    如果要在 Windows 里访问 WSL 里的文件,它们位于 C:\Users\你的用户名\AppData\Local\lxss\

    有待解决的问题

    有了 WSL,各种依赖 Linux 环境的东西都可以随便跑了,利用 apt-get 安装更新也都很方便。侦听端口要完全没有问题。但其实两个环境是隔绝的,Windows 里不能执行 Ubuntu 的东西,反之亦然。这样一来,诸如 Phpstorm 的 File Watcher 包括测试都无法正常发挥作用。解决方案有待进一步发掘。

    临时解决方案就是通过命令行启动各种 watch,比如

    sass watch

    Yarn

    在非 ~/ 开始的目录里执行 yarn install 有一定机率会产生错误,具体原因不明,暂时无法稳定重现,反复重试之后倒是也能装成功。这个是 WSL 的锅,只能等待 Windows 更新。

    总结

    时代在发展,科技在进步。按照 GitHub 刚刚发布的报告,微软已经是开源贡献第一大公司,Windows 拥抱 Linux 之后我们也可以在 Windows 下进行PHP开发了。世界真美好啊!

    顺便Ubuntu新版本也比老版本容易配置了,当然,也可能是我之前不会。


    参考:

  • MySQL 同表复制数据

    MySQL 同表复制数据

    我觉得再这么下去,我真敢说我写过PHP了……

    需求很简单,在同一个表中复制数据。以前的代码是在PHP里先 select *,然后 extract 成变量,再组合成一个大 sql,最后插入。我觉得这样不好,首先要执行两次 sql,其次写那么一大篇 sql 也挺麻烦的。于是研究了下,发现并不复杂,这里总结一下:

    如果是从别的表里导入数据,可以这样写:

    INSERT INTO `table`
    SELECT *
    FROM `table2`
    WHERE `id`=1
    

    如果是同表,并且表里没有主键,这样也好使;但是有主键的话,会被告知主键重复,这个时候就只能把字段都写出来了:

    # 假设表结构为 id, col1, col2, col3
    INSERT INTO `table`
    SELECT NULL, col1, col2, col3
    FROM `table`
    WHERE `id`=1
    

    这里字段的顺序很重要,要参照表的顺序来写(我是用 MySQL Wordbench 连上库,然后用Alert table 看的);不过好处在于,如果我们需要更改其中某个字段的值,只要在 sql 里直接写就好。比如我们复制后想交换后两个字段,或者改变某个字段的值,就可以这样:

    # 假设表结构为 id, name, age, sex
    # 交换age和sex
    INSERT INTO `table`
    SELECT NULL, `name`, `sex`, `age`
    FROM `table`
    WHERE `id`=1
    
    # 更改某字段的值:将复制得到的name加上copy_前缀
    INSERT INTO `table`
    SELECT NULL, CONCAT('copy_', `name`), `age`, `sex`
    FROM `table`
    WHERE `id`=1
    

    id 这种自增的主键,直接插入 NULL 就可以了,MySQL 会自动帮我们补全(如前几段所示)。

    同时插入多条数据也很简单,这样即可:

    # 假设表结构为id, name, age, sex
    # 复制 id<10 的字段,加上copy_标识
    INSERT INTO `table`
    SELECT NULL, CONCAT('copy_', `name`), `age`, `sex`
    FROM `table`
    WHERE `id`<10
    

    实话实说我不太懂MySQL,不过这样写我觉得有几个好处:

    1. 省事儿,可以不再把变量一一抄上
    2. 可以一次复制多条数据
    3. 只执行一行sql,速度应该会更快