sudo
表示 “superuser do”。它允许已验证的用户以其他用户的身份来运行命令。其他用户可以是普通用户或者超级用户。然而,绝大部分时候我们用它来以提升的权限来运行命令。
sudo
命令与安全策略配合使用,默认安全策略是 sudoers
,可以通过编辑文件 /etc/sudoers
来配置。其安全策略具有高度可拓展性。人们可以开发和分发他们自己的安全策略作为插件。
sudo
与 su 的区别
在 GNU/Linux 中,有两种方式可以用提升的权限来运行命令:
su 表示 “switch user”。使用 su 命令,我们可以切换到 root 用户并且执行命令,但是这种方式存在一些缺点:
sudo
以独特的方式解决了这些问题:
sudo
用户的所有操作都会被记录下来,管理员可以随时审查 sudo 用户执行了哪些操作sudo
用户的访问,我们可以限制用户只能执行某些命令在基于 Debian 的 GNU/Linux 中,所有活动都记录在 /var/log/auth.log
文件中
/etc/sudoers
文件记录着谁可以用 sudo 命令来提升权限,添加普通用户为 sudo
用户
编辑 /etc/sudoers 文件最有效的方式是使用 visudo
命令:
sudo visudo
添加以下行来允许用户 einverne 有 sudo
权限:
einverne ALL=(ALL) ALL
%admin ALL=(ALL) ALL
上述命令中:
%admin
的表示 admin 用户组授予 sudo 访问权限sudo
sudo
命令被允许以任何用户身份执行如果打开默认的 visudo
可以发现已经配置这样一行
root ALL=(ALL:ALL) ALL
这一行表示的含义是,用户 root, 登录任何 hostname, 可以在任何用户或者组下运行任何命令。
user hostname=(runas-user:runas-group) command
这里有一个需要稍微注意的地方,括号中能看到有 (ALL)
和 (ALL:ALL)
的区别,如果仅仅使用 (ALL)
那么,sudo 无法使用 -g
来指定用户组运行程序。1
如果发现在该配置中已经配置了 %admin ALL=(ALL) ALL
,那么把用户加入到该 admin 组中,就不需要另外配置该用户了。
usermod -aG sudo einverne
注:/etc/sudoers
文件必须以 visudo
命令来修改,该命令可以防止因为文件格式错误而导致问题,如果visudo
默认的编辑器不是你常用的编辑,可以通过如下方法来修改
sudo update-alternatives --config editor
上面提到编辑 /etc/sudoers
文件给用户添加 sudo 权限,不过还有一种更加简单的方法就是将用户添加到 sudo 或者 admin 组,修改 /etc/group
文件,然后在其中添加
sudo:x:27:username,username1
然后该 username 用户提升到 sudo 组(默认 sudo 组应该在 /etc/sudoers 中配置好权限)即可使用 sudo 命令。
要用提升的权限执行命令,只需要在命令前加上 sudo
,如下所示:
sudo cat /etc/passwd
当你执行这个命令时,它会询问用户 einverne 的密码,而不是 root 用户的密码。
除此之外,我们可以使用 sudo
以另一个用户身份执行命令。例如,在下面的命令中,用户 einverne 以用户 demo 的身份执行命令:
sudo -u demo whoami
[sudo] password for einverne:
demo
sudo
的一个限制是 —— 它无法使用 Shell 的内置命令。例如, history 记录是内置命令,如果你试图用 sudo
执行这个命令,那么会提示如下的未找到命令的错误:
sudo history
[sudo] password for einverne:
sudo: history: command not found
为了克服上述问题,我们可以访问 root shell,并在那里执行任何命令,包括 Shell 的内置命令。
要访问 root shell, 执行下面的命令:
sudo bash
执行完这个命令后——您将观察到提示符变为井号(#)。
这里将列举一些常用的 sudo 小技巧,可以用于日常任务。
让我们假设你想用提升的权限执行之前的命令,那么下面的技巧将会很有用:
sudo !4
上面的命令将使用提升的权限执行历史记录中的第 4 条命令。
很多时候,我们编辑系统的配置文件时,在保存时才意识到我们需要 root 访问权限来执行此操作。因为这个可能让我们丢失我们对文件的改动,我们可以在 Vim 中使用下面的命令来解决这种情况:
:w !sudo tee %
上述命令中:
sudo
和 tee 都是 shell 命令至今我们用 sudo
只执行了单个命令,但我们可以用它执行多个命令。只需要用分号 (;) 隔开命令,如下所示:
sudo -- bash -c 'pwd; id;'
上述命令中
使用 -l
参数可以用来查看当前用户可执行的 sudo 命令
sudo -l
当第一次执行 sudo
命令时,它会提示输入密码,默认情形下密码会被缓存 15 分钟。可以使用 NOPASSWD 关键字来禁用密码认证:
# User privilege specification
username ALL=(ALL) NOPASSWD:ALL 单独配置一个用户
# Allow members of group sudo to execute any command
%sudo ALL=(ALL) NOPASSWD:ALL 配置一组用户
解释一下该文件可以发现,每一行定义了一个配置,用户组需要使用 %
来区分。
为了提供受控访问,我们可以限制 sudo
用户只能执行某些命令。例如,下面的行只允许执行 echo 和 ls 命令 。
einverne ALL=(ALL) NOPASSWD: /bin/echo /bin/ls
sudo
让我们进一步深入了解 sudo
命令。
ls -l /usr/bin/sudo
-rwsr-xr-x 1 root root 145040 Jun 13 2017 /usr/bin/sudo
如果仔细观察文件权限,则发现 sudo
上启用了 setuid 位。当任何用户运行这个二进制文件时,它将以拥有该文件的用户权限运行。在所示情形下,它是 root 用户。
为了显示这一点,我们可以使用 id 命令,如下所示:
id
uid=1002(einverne) gid=1002(einverne) groups=1002(einverne)
当我们不使用 sudo
执行 id 命令时,将显示用户 einverne 的 id。
sudo id
uid=0(root) gid=0(root) groups=0(root)
但是,如果我们使用 sudo
执行 id 命令时,则会显示 root 用户的 id。
这里全部是推广链接,如果你觉得我的文章有用,帮忙点击一下可好?
看板应用
番茄工作法
民宿
从 14 年开始用,只遇到过一次宕机,虽然是 ovz 的但架个代理也完全足够了,况且两年才 15 刀干啥不续费。
用的日本的节点,稳得不行,5 刀一个月
Dropbox 用到如今最好的网盘,我也只推荐 Dropbox
路由器,电视盒子(机顶盒),大神都在这里
这是一个电子词典的论坛,[[GoldenDict]] 时获知,里面有各种电子词典分享,论坛中的人也很友好,千万不要注水
Git 使用过程中遇到的小技巧,平时没有 commit, merge, branch 用的那么勤快,但是需要时也需要查看一下,因此记录一下,以免忘记。
开发中经常使用分支开发,因此不可避免的在开发中向 dev,或者 bugfix 分支进行多次提交,而有些提交可能仅仅为了测试,commit message 也没有认认真真写,所以当开发完成,或者 bug 修复完成想要合并到 master 分支时,不希望保留中间糟糕的提交信息,有一种方法是使用 merge 的 --squash
。
而在之前我可能会用 soft reset 掉一些提交,然后重新合并为一次提交,而得知 merge 的 squash 之后,可以轻松将其他分之中的多次提交内容一次性合并到工作区中,然后使用 commit 作为提交。
git merge --squash <branch name>
# after
git commit -s
# then write your commit message
这里是 --squash
的解释:
Produce the working tree and index state as if a real merge happened (except for the merge information), but do not actually make a commit or move the HEAD, nor record $GIT_DIR/MERGE_HEAD to cause the next git commit command to create a merge commit. This allows you to create a single commit on top of the current branch whose effect is the same as merging another branch (or more in case of an octopus).
其实如果不介意数一下提交次数的话使用 git rebase -i 也是可以实现的,不过这个就是在自己的 feature 分支上先将所有的零碎提交合并成一次,然后再 merge 了。
有的时候会做了一些提交,但经过 review 或者中途发现变化需要丢弃的时候经常用 git reset
来丢掉一些 commit,一般情况下我都会使用 git reset --soft HEAD~1
来丢掉上一个提交,给自己重新检查一下上一次提交的内容。而有时可能不注意直接 git reset --hard <commit-id>
直接丢弃了好几个提交。等敲完回车才追悔莫及,此时就凸显了 git 的强大之处。其实在 Git 中做过的所有提交记录,都是有保存的,每一次修改 HEAD 的操作都被记录到了本地。
git 有一个命令 git reflog
可以查看所有对 HEAD 的变更操作,使用 reflog 命令找到需要恢复的 commit id 然后使用 git reset --hard <commit-id>
来恢复到那一次提交就可以了。
关联本地分支和远程分支,一般情况下使用 git push 时,直接将本地分支推送到远程同名分支,但是如果新项目不是 clone 远程,或者中途曾经更改了 remote,那么有可能 git 就不知道本地分支对应的远程分支,这时候使用 push 或者 pull 的时候就有可能会出错。
使用
git branch --set-upstream-to=origin/master master
来将本地 master 分支关联到 origin/master 分支。
或者也可以在 push 时自动关联上
git push -u origin master
本地做了很多修改,而想要放弃其中某一次提交可以使用 git rebase -i
, 对于最后一次提交可以使用 git reset --hard HEAD~1
来撤销
对于之前的提交,如果想要删除,可以使用
git rebase -i HEAD~N
来查看本地前 N 次提交,然后编辑文件删除某一次 commit 即可。更多的信息可以参考 Git book
需要注意的是,rebase 交互界面出现的 commit 由老到新,使用下面的命令比如 squash 则会向上合并。
PS. 不要用来改变已经 push 到远端的提交,除非明确的知道想要做的事情,可以使用 force push.
虽然这个操作不是经常需要具体做,但是有的时候不免会遇到,记录一下
git branch -m <new-name>
git whatchanged --since='2 weeks ago'
逛博客看到别人在讨论 [[短 URL 的设计实现]],然后偶然间发现了 GitHub 曾经推出 1 过的短域名服务 Git.io
创建短域名
curl -i https://git.io -F "url=https://github.com/einverne"
HTTP/1.1 100 Continue
HTTP/1.1 201 Created
Server: Cowboy
Connection: keep-alive
Date: Sun, 14 May 2017 03:05:40 GMT
Status: 201 Created
Content-Type: text/html;charset=utf-8
Location: https://git.io/v97cY
Content-Length: 27
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Runtime: 0.312051
X-Node: 09a65813-05e0-40a2-a9bf-6dd88da1cdbc
X-Revision: 392798d237fc1aa5cd55cada10d2945773e741a8
Strict-Transport-Security: max-age=31536000; includeSubDomains
Via: 1.1 vegur
使用短域名 302 跳转
curl -i https://git.io/v97cY
HTTP/1.1 302 Found
Server: Cowboy
Connection: keep-alive
Date: Sun, 14 May 2017 03:06:58 GMT
Status: 302 Found
Content-Type: text/html;charset=utf-8
Location: https://github.com/einverne
Content-Length: 0
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-Runtime: 0.005605
X-Node: d567f758-ba0e-4e8b-95ba-b6a80730cc20
X-Revision: 392798d237fc1aa5cd55cada10d2945773e741a8
Strict-Transport-Security: max-age=31536000; includeSubDomains
Via: 1.1 vegur
还可以使用 code
参数来指定生成的短链接名字,比如
curl -i https://git.io -F "url=https://github.com/...." -F "code=abcd"
git.io 缩短的域名必须是 github 站相关的域名,其他网站的地址它是不会缩短的。并且每个链接只能被缩短一次,如果第二次再请求会返回和上一次缩短一样的结果。
所以无奈啦,
Google 的短域名服务其实已经用很久了 https://goo.gl/ ,相比来说,有几个好处
.qr
可以查看二维码,比如 https://goo.gl/xEeWKp 添加 qr https://goo.gl/xEeWKp.qrYOURLS 项目,使用 PHP 实现短域名 https://github.com/YOURLS/YOURLS 项目到目前已经非常完善了。
更多的项目可以参考:https://github.com/topics/url-shortener
PHP 版本 https://github.com/takashiki/Ourls
一个比较好玩的 JS 纯前端实现,将跳转信息保存到浏览器 Local Storage 中 2, 可以学习一下项目中对本地 Storage 操作的部分 3,应该挺有意思。
表结构设计
create table links
(
shortLink not null
unique,
longLink,
timestamp,
ip,
redirectMethod
);
今天看 JDK 的路径突然发现我在两台机子上,一台装在了 /usr/local/
目录下,而我自己的 Mint 装在了 /opt/
目录下。感觉对 Linux 目录结构还需要增加了解,就Google了一下。
/usr/local
和 /opt
目录设计为存放非系统级命令,而 /usr/local
目录一般用来防止管理员通过本地编译安装的程序,比如通过 ./configure; make; make install
等命令安装的程序,该目录的目的就是为了使用户产生的命令不和系统命令产生冲突。
/opt
目录一般用来安装非捆绑的软件程序,每个应用都有其自己的子目录,比如在安装Chrome 之后,Chrome 完整的程序和其资源文件都会存在 /opt/google/chrome
下。
因此在 Linux 下如果手工安装 JDK 7/8 时,可以将安装路径手动指定到 /opt
目录下,方便管理。
安装 JDK 的两种方式,一种是直接通过 apt 包管理来安装
sudo add-apt-repository -y ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java8-installer
然后使用 java -version
来验证。
或者直接从官网下载压缩包,将文件内容解压到 /opt/java/
目录下。然后配置环境变量 JAVA_HOME
指向 Java bin 的目录。
wget http://..../jdk-8u91-linux-x64.tar.gz
tar -zxvf jdk-8u91-linux-x64.tar.gz -C /opt/java/
vim ~/.zshrc # or ~/.bashrc
添加
export JAVA_HOME=/opt/java/jdk1.8.0_91/
export PATH="$PATH:$JAVA_HOME/bin/"
使环境变量生效
source ~/.zshrc
运行命令会得到目前系统安装的 JDK 或者 JRE,选择序号确定即可。
$ sudo update-alternatives --config java
There are 3 choices for the alternative java (providing /usr/bin/java).
Selection Path Priority Status
------------------------------------------------------------
* 0 /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java 1071 auto mode
1 /usr/lib/jvm/java-6-openjdk-amd64/jre/bin/java 1061 manual mode
2 /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java 1071 manual mode
3 /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java 1081 manual mode
Press enter to keep the current choice[*], or type selection number:
在更改 SQLAlchemy Session 从每次请求都创建到共享同一个 Session 之后遇到了如下问题:
StatementError: (sqlalchemy.exc.InvalidRequestError) Can’t reconnect until invalid transaction is rolled back [SQL: ]
或者是
raised unexpected: OperationalError(“(_mysql_exceptions.OperationalError) (2006, ‘MySQL server has gone away’)”,)
错误是 SQLAlchemy 抛出。原因是你从 pool 拿的 connection 没有以 session.commit 或 session.rollback 或者 session.close 放回 pool 里。这时 connection 的 transaction 没有完结(rollback or commit)。 而不知什么原因(recyle 了,timeout 了)你的 connection 又死掉了,你的 sqlalchemy 尝试重新连接。由于 transaction 还没完结,无法重连。
正确用法是确保 session 在使用完成后用 session.close, session.commit 或者 session.rollback 把连接还回 pool。
Session 是一个和数据库交互的会话。在 SQLAlchemy 中使用 Session 来创建和管理数据库连接的会话。
sessions 和 connections 不是相同的东西, session 使用连接来操作数据库,一旦任务完成 session 会将数据库 connection 交还给 pool。
在使用 create_engine
创建引擎时,如果默认不指定连接池设置的话,一般情况下,SQLAlchemy 会使用一个 QueuePool 绑定在新创建的引擎上。并附上合适的连接池参数。
在以默认的方法 create_engine 时(如下),就会创建一个带连接池的引擎。
engine = create_engine('mysql+mysqldb://root:password@127.0.0.1:3306/dbname')
在这种情况下,当你使用了 session 后就算显式地调用 session.close(),也不能把连接关闭。连接会由 QueuePool 连接池进行管理并复用。
这种特性在一般情况下并不会有问题,不过当数据库服务器因为一些原因进行了重启的话。最初保持的数据库连接就失效了。随后进行的 session.query() 等方法就会抛出异常导致程序出错。
如果想禁用 SQLAlchemy 提供的数据库连接池,只需要在调用 create_engine 是指定连接池为 NullPool,SQLAlchemy 就会在执行 session.close() 后立刻断开数据库连接。当然,如果 session 对象被析构但是没有被调用 session.close(),则数据库连接不会被断开,直到程序终止。
下面的代码就可以避免 SQLAlchemy 使用连接池:
#!/usr/bin/env python
#-*- coding: utf-8 -*-
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.pool import NullPool
engine = create_engine('mysql+mysqldb://root:password@127.0.0.1:3306/dbname', poolclass=NullPool)
Session = sessionmaker(bind=engine)
session = Session()
usr_obj_list = session.query(UsrObj).all()
print usr_obj_list[0].id
session.close()
create_engine()
函数和连接池相关的参数有:
-pool_recycle
, 默认为 -1, 推荐设置为 7200, 即如果 connection 空闲了 7200 秒,自动重新获取,以防止 connection 被 db server 关闭。-pool_size=5
, 连接数大小,默认为 5,正式环境该数值太小,需根据实际情况调大-pool_timeout=30
, 获取连接的超时阈值,默认为 30 秒直接只用 create_engine
时,就会创建一个带连接池的引擎
engine = create_engine('postgresql://postgres@127.0.0.1/dbname')
当使用 session 后就显示地调用 session.close(),也不能把连接关闭,连接由 QueuePool 连接池管理并复用。
引发问题
当数据库重启,最初保持的连接就会失败,随后进行 session.query()
就会失败抛出异常 mysql 数据 ,interactive_timeout 等参数处理连接的空闲时间超过(配置时间),断开
基本
确保 transaction 有非常清晰的开始和结束,保持 transaction 简短,也就意味着让 transaction 能在一系列操作之后终止,而不是一直开放着。
from contextlib import contextmanager
@contextmanager def session_scope(): “"”Provide a transactional scope around a series of operations.””” session = Session() try: yield session session.commit() except: session.rollback() raise finally: session.close()
Session 不是为了线程安全而设计的,因此确保只在同一个线程中使用。
如果实际上有多个线程参与同一任务,那么您考虑在这些线程之间共享 Session 及其对象;但是在这种极不寻常的情况下,应用程序需要确保实现正确的 locking scheme,以便不会同时访问 Session 或其状态。处理这种情况的一种更常见的方法是为每个并发线程维护一个 Session,而是将对象从一个 Session 复制到另一个 Session,通常使用 Session.merge() 方法将对象的状态复制到本地的新对象中。
想要线程安全时使用 scoped_session()
,文档解释
the scoped_session() function is provided which produces a thread-managed registry of Session objects. It is commonly used in web applications so that a single global variable can be used to safely represent transactional sessions with sets of objects, localized to a single thread.
using transactional=False
is one solution, but a better one is to simply rollback(), commit(), or close() the Session when operations are complete - transactional mode (which is called “autocommit=False” in 0.5) has the advantage that a series of select operations will all share the same isolated transactional context..this can be more or less important depending on the isolation mode in effect and the kind of application.
DBAPI has no implicit “autocommit” mode so there is always a transaction implicitly in progress when queries are made.
This would be a fairly late answer. This is what happens: While using the session, a sqlalchemy Error is raised (anything which would also throw an error when be used as pure SQL: syntax errors, unique constraints, key collisions etc.).
You would have to find this error, wrap it into a try/except-block and perform a session.rollback().
After this you can reinstate your session.
记录一些 SSH 相关的内容,经常使用。
SSH 是 Secure Shell 的缩写,是一个应用层的加密网络协议,它不只可以用于远程登录,远程命令执行,还可用于数据传输。 当然它由 ssh Client 和 ssh Server 端组成,有很多实现,Ubuntu 上就默认安装的 OpenSSH, Client 端叫做 ssh, Server 端叫做 sshd. OpenSSH 只用来做远程登录和命令执行。
查看本地 ~/.ssh/
目录是否有 id_rsa.pub
,如果没有,在本地创建公钥
ssh-keygen -t rsa
一路回车到底
然后把本地公钥复制到远程机器的 ~/.ssh/
目录下,并命名为 authorized_keys
scp ~/.ssh/id_rsa.pub username@hostname:~/.ssh/authorized_keys
# or
ssh-copy-id -i ~/.ssh/id_rsa.pub username@hostname
如果远程主机配置了多台机器免密登录,最好将 id_ras.pub
追加而不是覆盖到 authorized_keys
cat id_rsa.pub >> .ssh/authorized_keys
SSH 总是被强行中断,导致效率低下,可以在服务端配置,让 server 每隔 30 秒向 client 发送一个 keep-alive 包来保持连接:
vim /etc/ssh/sshd_config
添加
ClientAliveInterval 30
ClientAliveCountMax 60
第二行配置表示如果发送 keep-alive 包数量达到 60 次,客户端依然没有反应,则服务端 sshd 断开连接。如果什么都不操作,该配置可以让连接保持 30s*60 , 30 min
重启本地 ssh
sudo service ssh restart
如果找不到 ssh,”Failed to restart ssh.service: Unit ssh.service not found.” ,需要安装
sudo apt-get install openssh-server
如果服务端没有权限配置,或者无法配置,可以配置客户端 ssh,使客户端发起的所有会话都保持连接:
vim /etc/ssh/ssh_config
添加
ServerAliveInterval 30
ServerAliveCountMax 60
本地 ssh 每隔 30s 向 server 端 sshd 发送 keep-alive 包,如果发送 60 次,server 无回应断开连接。
下面是 man ssh_config
的内容
ServerAliveCountMax Sets the number of server alive messages (see below) which may be sent without ssh(1) receiving any messages back from the server. If this threshold is reached while server alive messages are being sent, ssh will disconnect from the server, terminating the session. It is important to note that the use of server alive messages is very different from TCPKeepAlive (below). The server alive messages are sent through the encrypted channel and therefore will not be spoofable. The TCP keepalive option enabled by TCPKeepAlive is spoofable. The server alive mechanism is valuable when the client or server depend on knowing when a connection has become inactive.
The default value is 3. If, for example, ServerAliveInterval (see below) is set to 15 and ServerAliveCountMax is left at the default, if the server becomes unresponsive, ssh will disconnect after approximately 45 seconds. This option applies to protocol version 2 only; in protocol version 1 there is no mechanism to request a response from the server to the server alive messages, so disconnection is the responsibility of the TCP stack.
ServerAliveInterval Sets a timeout interval in seconds after which if no data has been received from the server, ssh(1) will send a message through the encrypted channel to request a response from the server. The default is 0, indicating that these messages will not be sent to the server, or 300 if the BatchMode option is set. This option applies to protocol version 2 only. ProtocolKeepAlives and SetupTimeOut are Debian-specific compatibility aliases for this option.
如果需要在多个窗口中打开同一个服务器连接,可以尝试添加 ~/.ssh/config
,添加两行
ControlMaster auto
ControlPath ~/.ssh/connection-%r@%h:%p
配置之后,第二条连接共享第一次建立的连接,加快速度。
添加长连接配置
ControlPersist 4h
每次 SSH 连接建立之后,此条连接会被保持 4 小时,退出服务器之后依然可以重用。
配置连接中转
ForwardAgent yes
当需要从一台服务器连接另外一个服务器,而在两台服务器中传输数据时,可以不用通过本地电脑中转,直接配置以上 ForwardAgent 即可。
最终, ~/.ssh/config
下的配置:
Host *
ForwardAgent yes
ServerAliveInterval 3
ServerAliveCountMax 20
TCPKeepAlive no
ControlMaster auto
ControlPath ~/.ssh/connection-%r@%h:%p
ControlPersist 4h
Compression yes
之前写过一篇文章总结,在同一台机器上同时使用 GitHub,和 GitLab 的 key,可以具体参考这里
MySQL 命令行操作相关内容,防止遗忘。MySQL 常用命令记录,总结。
Under Ubuntu
sudo apt update
sudo apt-get install mysql-server
sudo mysql_secure_installation
如果安装过程中没有弹出设置密码的对话,那么可以在安装完成后执行:
sudo mysql_secure_installation
来设置密码,及一些安全的设置。之后就可以用
sudo mysql -u root -p
来登录。
可以使用如下命令启动,停止,重启 MySQL 服务
sudo /etc/init.d/mysql {start | stop | status | restart}
sudo service mysql {start | stop | status | restart}
Windows 下可以使用 net 命令
net start mysql
同理,启动其他比如微软自己的 SQL Server 可以使用
net start mssqlserver
# 或者重启 tomcat
net start tomcat6
终端下输入
mysql -u [root] -p
之后输入 root 的密码
需要注意:
常用命令中也大致可以分成几类,一类是通用命令,包括查看 MySQL 数据库,及查看基本表结构的。还有就是创建修改表结构,最后最常用的就是增删改查数据的命令。
查看数据库
SHOW DATABASES;
输出
mysql> show DATABASES;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| parker |
| performance_schema |
| sys |
| test |
| youku |
+--------------------+
7 rows in set (0.00 sec)
创建数据库
CREATE DATABASE dbname;
删除数据库
DROP DATABASE dbname;
使用数据库
USE dbname;
显示数据库中表
SHOW tables;
创建表
CREATE TABLE table_name (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20) , signup_date DATE);
查看表结构
DESCRIBE table_name;
增加新列
ALTER TABLE table_name **ADD COLUMN** [column] VARCHAR(40);
比如新增一列自增 id
ALTER TABLE [table] ADD COLUMN [column] int NOT NULL AUTO_INCREMENT PRIMARY KEY;
如果想要自定义新列的位置,可以使用 AFTER
ALTER TABLE table_name **ADD** email VARCHAR(40) AFTER name;
删除列
ALTER TABLE table_name DROP column
修改列,或者修改列类型
ALTER TABLE tablename **MODIFY COLUMN** column_name VARCHAR(20);
ALTER TABLE tablename **ALTER COLUMN** column_name VARCHAR(20);
修改表结构,添加组合 Primary Key,将两列数据作为 PK
如果 PRIMARY KEY 不存在
ALTER TABLE [table] ADD primary key(column1, column2);
如果 PRIMARY KEY 存在
ALTER TABLE [table] DROP PRIMARY KEY, ADD primary key(column1, column2);
修改表名
ALTER TABLE origin_table_name RENAME TO new_table_name
修改自增ID:
ALTER TABLE table_name AUTO_INCREMENT = 1000;
Like 通配符
_
一个未知字符串插入记录
INSERT INTO `table_name` (`name`, `signup_date`) VALUES ("Verne", "2017-05-01");
更新记录
UPDATE [table] SET [column] = 'Y' WHERE `potluck`.`name` ='Sandy';
删除一行记录
DELETE from table_name where column_name = "value";
获取记录条数
SELECT COUNT([column]) FROM [table];
模糊查询
SELECT * FROM [table] WHERE [column] LIKE '%value%';
排序
SELECT * FROM [table] WHERE [column] ORDER BY [column] ASC LIMIT [value];
Order 可以使用 DESC
, ASC
删除表中所有记录
TRUNCATE table [table]
查看创建表 DDL
show create table [table_name];
查看表的索引
SHOW INDEX FROM [table];
导出数据
mysqldump -u [username] -p [database] > db_backup.sql
mysqldump -u [username] -p [database] [table_name] > db_backup.sql
导入还原数据
mysql -u [username] -p [database] < db_backup.sql
mysql -u [username] -p -h localhost [database] < db_backup.sql
查看数据库中所有用户
SELECT User,Host FROM mysql.user;
创建新用户:
CREATE USER 'username'@'localhost' IDENTIFIED BY 'password';
记住这里的 localhost,可以替换成客户端的 IP,或者任何要需要授权访问的 IP 地址。
创建新用户时,如果密码选择太简单可能会导致密码安全检查无法过去,这时可以设置 MySQL 的 validate_password_policy
来使用简单密码:
mysql> set global validate_password_policy=0;
在 0 或者 LOW 下,密码只验证长度。在 1 或者 MEDIUM 下,密码会验证长度,必须包含数字,大小写,特殊字符。在 2 或者 STRONG 下,还会验证是否在字典中。
其中
mysql> select @@validate_password_length;
指定了使用密码的长度,默认为 8 位。
授予用户某个数据库全部权限
GRANT ALL ON [database].* TO 'user'@'localhost';
授予某个用户全部的权限:
GRANT ALL PRIVILEGES ON *.* TO 'user'@'localhost' WITH GRANT OPTION;
修改密码
mysqladmin -u root -p old_password password new_password
删除用户
DROP USER 'user1'@'localhost';
如果在创建新用户时提醒密码 weak,则可以使用如下命令来禁用密码校验
uninstall plugin validate_password;
// MySQL 8.0.4 以上
UNINSTALL COMPONENT 'file://component_validate_password';
将表从一个 schema 中移动到另外的 schema 中
alter table old_db.table_name rename new_db.table_name
mysql -h host -u user -p < batch-file
如果想要远程通过 root 连接 MySQL,先查看一下 MySQL 配置 /etc/mysql.my.cnf
,需要注释其中
#bind-address = 127.0.0.1
默认 3306 端口只允许本地访问,然后重启 /etc/init.d/mysql restart
。
修改 MySQL 数据库中的 user 表,使得 root 能够远程登录
mysql -u root –p
mysql>use mysql;
mysql>update user set host = '%' where user = 'root';
mysql>select host, user from user;
可以通过如下语句查看 MySQL 密码规则:
SHOW VARIABLES LIKE 'validate_password%';
mysql> SHOW VARIABLES LIKE 'validate_password%';
+--------------------------------------+--------+
| Variable_name | Value |
+--------------------------------------+--------+
| validate_password_check_user_name | OFF |
| validate_password_dictionary_file | |
| validate_password_length | 8 |
| validate_password_mixed_case_count | 1 |
| validate_password_number_count | 1 |
| validate_password_policy | MEDIUM |
| validate_password_special_char_count | 1 |
+--------------------------------------+--------+
7 rows in set (0.01 sec)
改变密码策略:
SET GLOBAL validate_password_policy=MEDIUM; // LOW MEDIUM HIGH
SET GLOBAL validate_password_policy=1; // Low=0 Medium=1 High=2
通过如下方式创建用户并赋予权限。
mysql> CREATE USER 'einverne'@'localhost' IDENTIFIED BY 'some_pass';
mysql> GRANT ALL PRIVILEGES ON *.* TO 'einverne'@'localhost'
-> WITH GRANT OPTION;
mysql> CREATE USER 'einverne'@'%' IDENTIFIED BY 'some_pass';
mysql> GRANT ALL PRIVILEGES ON *.* TO 'einverne'@'%'
-> WITH GRANT OPTION;
FLUSH PRIVILEGES;
解决问题的思路,看看防火墙是不是开着,检查 MySQL 用户的权限设置是否正确。
一般在新安装之后首次登录的时候会出现这个错误。
在 Ubuntu 下,MySQL 服务器默认使用了 UNIX auth socket plugin,这个时候必须要使用 sudo mysql -u root -p
通过如下 SQL 可以看到 root 使用了 unix_socket
插件
ERROR 1698 (28000): Access denied for user 'root'@'localhost'
MariaDB [(none)]> SELECT User, Host, plugin FROM mysql.user;
+------+-----------+-------------+
| User | Host | plugin |
+------+-----------+-------------+
| root | localhost | unix_socket |
+------+-----------+-------------+
1 row in set (0.00 sec)
有两种方式可以解决这个问题:
mysql_native_password
插件来设置 root 用户sudo mysql -u root
mysql> USE mysql;
mysql> UPDATE user set plugin='mysql_native_password' WHERE user='root';
mysql>FLUSH PRIVILEGES;
mysql>exit
然后重启服务:
sudo service mysql restart
$ sudo mysql -u root # I had to use "sudo" since is new installation
mysql> USE mysql;
mysql> CREATE USER 'YOUR_SYSTEM_USER'@'localhost' IDENTIFIED BY 'YOUR_PASSWD';
mysql> GRANT ALL PRIVILEGES ON *.* TO 'YOUR_SYSTEM_USER'@'localhost';
mysql> UPDATE user SET plugin='auth_socket' WHERE User='YOUR_SYSTEM_USER';
mysql> FLUSH PRIVILEGES;
mysql> exit;
重启服务:
$ sudo service mysql restart
在使用 mysql 命令行的时候如果查询结果比较大的时候,可以在语句后面加上 \G;
来将结果垂直方式展现,可以更好的查看结果。那么 \G 到底是什么意思呢。
我们都知道 sql 语句需要使用分号 ;
来结束,事实上分号是 \g
的速记。go 命令在 sql 的历史中曾经存在,现在有些批量语句也可以通过 go 命令来提交给 server 执行。\G
命令似乎继承了 \g
命令的字母,大写来表示另外一种行为,查看 help 能看到
mysql> help
...
\g go Send command to mysql server.
\G ego Send command to mysql server, display result vertically.
...
在上述的 help 中也会发现 mysql 有一个 ego
命令,字母 e 表示的是垂直模式,然而在从 mysql 的选项来看 mysql --vertical
或者 mysql -E
可以开启使用垂直方式显示结果的行为
man mysql...
...
--vertical, -E
Print query output rows vertically (one line per column value).
Without this option, you can specify vertical output for individual
statements by terminating them with \G.
...
你为什么使用 -E
来作为垂直模式呢,因为 -V
, -v
和 -e
都已经被占用有别的行为了。
Python 2.x 中使用 MySQLdb 来连接 MySQL 数据库。在 Python 3.x 中使用 P 有 MySQL,使用方式 import pymysql
,而其他操作几乎一致。
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import MySQLdb
"""
pip install MySQL-python
MySQLdb 是用于 Python 链接 Mysql 数据库的接口,它实现了 Python 数据库 API 规范 V2.0,基于 MySQL C API 上建立的。
在使用 Python 连接之前确保已经有数据表建立
> mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 25
Server version: 5.7.18-0ubuntu0.16.04.1 (Ubuntu)
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| spy |
| sys |
+--------------------+
5 rows in set (0.00 sec)
mysql> CREATE DATABASE testdb;
Query OK, 1 row affected (0.00 sec)
mysql> CREATE USER 'testuser'@'localhost' IDENTIFIED BY '12345678';
Query OK, 0 rows affected (0.01 sec)
mysql> USE testdb;
Database changed
mysql> GRANT ALL ON testdb.* TO 'testuser'@'localhost';
Query OK, 0 rows affected (0.00 sec)
"""
con = MySQLdb.connect('localhost', 'testuser', '12345678', 'testdb', charset='utf8')
def create_table():
with con:
cur = con.cursor()
cur.execute("DROP TABLE IF EXISTS Users")
cur.execute("CREATE TABLE Users(id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50))")
def insert(name):
with con:
cur = con.cursor()
cur.execute("INSERT INTO Users(name) VALUES('" + name + "')")
def get():
with con:
cur = con.cursor()
cur.execute("SELECT * FROM Users")
rows = cur.fetchall()
for row in rows:
print row
def update(old_name, new_name):
with con:
cur = con.cursor()
cur.execute("UPDATE Users SET name = %s WHERE name = %s", (new_name, old_name))
def transaction():
try:
con.cursor()
insert("CC")
insert("David")
insert("Einstein")
con.commit()
except MySQLdb.Error, e:
if con:
con.rollback()
print "Error %d: %s" % (e.args[0],e.args[1])
if __name__ == '__main__':
try:
cur = con.cursor()
sql = "SELECT VERSION()"
cur.execute(sql)
version = cur.fetchone()
print "MySQL version : %s " % version
except MySQLdb.Error, e:
print "Error %d: %s" % (e.args[0], e.args[1])
if con:
con.close()
create_table()
insert("Alex")
insert("Verne")
get()
update("Alex", "Bob")
get()
transaction()
get()
if con:
con.close()
一个更加详细的 Cheatsheet
https://gist.github.com/einverne/0c256fe6351a89c7815b75f0d9964bfe
mysql 自带的命令行工具不会自动补全,这里推荐 mycli
可以实现 MySQL 命令行的自动补全和语法高亮。
使用 pip 安装
pip install mycli
其基本使用和 mysql 命令行基本一致
mycli [OPTIONS] [DATABASE]
Options:
-h, --host TEXT Host address of the database.
-P, --port INTEGER Port number to use for connection. Honors
$MYSQL_TCP_PORT
-u, --user TEXT User name to connect to the database.
-S, --socket TEXT The socket file to use for connection.
-p, --password TEXT Password to connect to the database
--pass TEXT Password to connect to the database
--ssl-ca PATH CA file in PEM format
--ssl-capath TEXT CA directory
--ssl-cert PATH X509 cert in PEM format
--ssl-key PATH X509 key in PEM format
--ssl-cipher TEXT SSL cipher to use
--ssl-verify-server-cert Verify server's "Common Name" in its cert
against hostname used when connecting. This
option is disabled by default
-v, --version Version of mycli.
-D, --database TEXT Database to use.
-R, --prompt TEXT Prompt format (Default: "\t \u@\h:\d> ")
-l, --logfile FILENAME Log every query and its results to a file.
--defaults-group-suffix TEXT Read config group with the specified suffix.
--defaults-file PATH Only read default options from the given file
--myclirc PATH Location of myclirc file.
--auto-vertical-output Automatically switch to vertical output mode
if the result is wider than the terminal
width.
-t, --table Display batch output in table format.
--csv Display batch output in CSV format.
--warn / --no-warn Warn before running a destructive query.
--local-infile BOOLEAN Enable/disable LOAD DATA LOCAL INFILE.
--login-path TEXT Read this path from the login file.
-e, --execute TEXT Execute query to the database.
--help Show this message and exit.
首先随便看看 Python 的镜像 就能看到非常多的版本,这些版本怎么选择呢。
首先解释几个常见的名词,其实都是 Linux 的发行版本
其次基于不同版本系统制作的镜像自身大小都有区别,比较精简的 python:2.7-slim 可能也要比 python:2.7-alpine 来的大。
最原始的 Scrapy 项目是只能将爬虫部署到单机上,如果要实现分布式爬虫就需要手动去维护一个待抓取的列表,那么 scrapy-redis 项目就是这样一个存在。
特性:
Scrapy 已经能够满足大部分爬虫的需要,但是有一些场景 Scrapy 并不适用。
pip install scrapy-redis
先要在 settings 中配置,具体参考官方文档,代码集成如下:
from scrapy_redis.spiders import RedisSpider
class MySpider(RedisSpider):
name = 'myspider'
def parse(self, response):
# do stuff
pass