Docker容器自动重启,restart=always参数

前言

服务器重启了,Docker容器没跟着起来?或者容器崩了,不自动重启?本文讲怎么配置自动重启。

一、restart参数

Docker用 --restart 参数控制容器的重启策略。

四种策略

策略 说明
no 不自动重启(默认)
on-failure 非正常退出时重启
always 总是自动重启
unless-stopped 除非手动停了,否则一直重启

二、always策略

用法

docker run -d --name nginx --restart=always nginx

什么时候会重启

  1. 容器崩了(非正常退出)
  2. Docker服务重启
  3. 服务器重启(只要Docker开机自启)

特点

  • 不管什么原因停了,都会自动起来
  • 服务器重启后也会自动启动

三、unless-stopped策略

用法

docker run -d --name nginx --restart=unless-stopped nginx

和always的区别

  • always:就算你手动停了,重启Docker后还是会起来
  • unless-stopped:如果你手动停了,重启Docker后就不起来了

推荐用unless-stopped,更灵活。

四、on-failure策略

用法

docker run -d --name nginx --restart=on-failure:5 nginx

说明

  • 只有非正常退出时才重启
  • :5 表示最多重启5次,超过就不重启了

什么时候用

比如批处理任务,失败了重试几次,一直失败就算了。

五、no策略(默认)

docker run -d --name nginx --restart=no nginx

不自动重启,停了就停了。

六、修改已有容器的重启策略

运行中的容器

docker update --restart=always 容器名

不用删容器,直接改。

例子

docker update --restart=always nginx
docker update --restart=unless-stopped mysql

七、Docker Compose里的配置

services:
  nginx:
    image: nginx
    restart: always

或者:

restart: unless-stopped

八、服务器开机自启

1. Docker服务开机自启

systemctl enable docker

2. 容器开机自启

只要容器设了 --restart=always 或 unless-stopped,Docker启动后容器就会自动启动。

验证

重启服务器试试:

reboot

重启完看看容器起来没:

docker ps

九、查看重启策略

docker inspect -f '{{.HostConfig.RestartPolicy.Name}}' 容器名

输出:

always

十、常见坑

坑1:设了always,服务器重启后没起来

原因: Docker服务没设开机自启。

解决:

systemctl enable docker

坑2:手动停了,又自动起来了

用了 always 策略,手动停了也会起来。

解决: 想彻底停掉:

docker update --restart=no 容器名
docker stop 容器名

坑3:一直重启,死循环

容器启动就崩,崩了又重启,死循环。

检查:

docker logs 容器名

看看为什么启动失败,先把问题修了。

坑4:on-failure重启次数

--restart=on-failure:5

最多重启5次,超过就不重启了。

注意: 这个5是次数,不是间隔。

十一、推荐配置

生产环境推荐

docker run -d \
  --name nginx \
  --restart=unless-stopped \
  -p 80:80 \
  nginx
  • 服务器重启自动起来
  • 手动停了就不自动起
  • 灵活

数据库推荐

docker run -d \
  --name mysql \
  --restart=unless-stopped \
  -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -v /data/mysql:/var/lib/mysql \
  mysql:5.7

总结

记住:

  1. --restart=always 总是自动重启
  2. --restart=unless-stopped 推荐,手动停了不自动起
  3. 改已有容器:docker update --restart=xxx 容器名
  4. 服务器开机自启要先 systemctl enable docker

最常用:

docker run -d --name nginx --restart=unless-stopped nginx

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-8 08:28 

Docker网络模式详解,bridge、host、none

前言

Docker容器怎么联网的?bridge、host、none这些网络模式啥区别?本文一次讲清楚。

一、Docker网络模式

Docker有5种网络模式:

  1. bridge:默认模式,桥接网络
  2. host:主机网络,和宿主机共用
  3. none:无网络
  4. container:和另一个容器共用网络
  5. 自定义网络:自己建的网络

二、bridge模式(默认)

是什么

Docker默认的网络模式。每个容器有自己的虚拟网络,通过docker0网桥和宿主机通信。

特点

  • 每个容器有自己的IP
  • 容器之间可以通过容器名互相访问
  • 外部要访问容器,要靠 -p 端口映射

用法

docker run -d --name nginx -p 80:80 nginx

没指定 --network,默认就是bridge。

容器之间通信

docker run -d --name mysql mysql:5.7
docker run -d --name web --link mysql nginx

或者用自定义网络,容器名直接解析:

docker network create mynet
docker run -d --name mysql --network mynet mysql:5.7
docker run -d --name web --network mynet nginx

web容器里直接用 mysql 这个主机名就能连数据库。

三、host模式

是什么

容器直接用宿主机的网络,没有自己的虚拟网卡。

特点

  • 没有独立IP,和宿主机共用
  • 不用 -p 映射,直接用宿主机的端口
  • 性能最好,因为少了一层网络转发
  • 安全性差点,容器直接暴露在宿主机网络上

用法

docker run -d --name nginx --network host nginx

注意

host模式下,-p 参数无效。容器用什么端口,直接就是宿主机的端口。

比如Nginx监听80,宿主机的80就被占了。

四、none模式

是什么

容器没有网络,完全断网。

特点

  • 没有网卡,没有IP
  • 只能通过localhost访问
  • 适合不需要联网的任务,比如批量处理文件

用法

docker run -d --name test --network none nginx

查看

docker exec test ip addr

只能看到lo回环网卡。

五、container模式

是什么

新创建的容器和一个已有的容器共用网络。

特点

  • 两个容器共用同一个网络命名空间
  • 用 127.0.0.1 就能互相访问
  • 适合一个应用拆成多个容器的场景

用法

# 先跑一个web容器
docker run -d --name web nginx

# 再跑一个sidecar容器,和web共用网络
docker run -d --name sidecar --network container:web busybox

sidecar容器里 127.0.0.1 就是web容器的localhost。

六、自定义网络

创建网络

docker network create mynet

使用网络

docker run -d --name mysql --network mynet mysql:5.7
docker run -d --name redis --network mynet redis

好处

  • 容器名自动解析,不用写IP
  • 网络隔离,不同网络的容器不能互相访问
  • 更灵活

查看网络

docker network ls

查看网络详情

docker network inspect mynet

七、常用命令

查看容器网络模式

docker inspect -f '{{.HostConfig.NetworkMode}}' 容器名

查看容器IP

docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 容器名

查看所有网络

docker network ls

删除网络

docker network rm mynet

八、常见场景选什么模式

场景1:跑Web服务,要外网访问

选bridge,用 -p 映射端口。

场景2:追求高性能,比如数据库

选host,少一层转发,性能好。

场景3:不需要联网的任务

选none,安全。

场景4:两个容器紧耦合

选container,共用网络。

场景5:多容器编排

选自定义网络,容器名自动解析。

九、常见坑

坑1:容器之间ping不通

bridge模式下,容器之间默认是通的,但如果用了none就不通。

检查: 网络模式是什么?

docker inspect 容器名 | grep NetworkMode

坑2:host模式下端口冲突

host模式直接用宿主机端口,端口被占了就起不来。

检查: 宿主机端口有没有被占。

坑3:容器名解析不了

默认bridge网络不支持容器名解析。

解决: 用自定义网络。

坑4:改了网络模式,要重新建容器

网络模式是容器创建时定的,不能改。

解决: 删掉容器重新跑。

总结

记住:

  1. bridge:默认,端口映射,常用
  2. host:共用宿主机网络,性能好
  3. none:断网,安全
  4. container:和别的容器共用网络
  5. 自定义网络:容器名自动解析,推荐

最常用:

docker network create mynet
docker run -d --name nginx --network mynet nginx

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-8 08:27 

Docker运行Nginx,挂载配置文件和网页

前言

用Docker跑Nginx,想改网站文件、改配置,每次进容器里改太麻烦。挂载出来就方便多了。本文讲怎么挂载网页和配置文件。

一、基本运行

最简单的跑法

docker run -d --name nginx -p 80:80 nginx

问题: 网站文件和配置都在容器里,改起来不方便。

二、挂载网页目录

完整命令

docker run -d \
  --name nginx \
  -p 80:80 \
  -v /data/www:/usr/share/nginx/html \
  nginx

解释

  • 宿主机 /data/www 目录
  • 容器里 /usr/share/nginx/html 目录

这样直接在宿主机改网页文件,刷新浏览器就生效了。

测试

在宿主机创建index.html:

echo "<h1>Hello Docker Nginx</h1>" > /data/www/index.html

浏览器访问:

http://服务器IP

就能看到了。

三、挂载配置文件

1. 先从容器里拷出默认配置

docker run -d --name nginx nginx
docker cp nginx:/etc/nginx/nginx.conf /data/nginx/nginx.conf
docker cp nginx:/etc/nginx/conf.d /data/nginx/conf.d
docker rm -f nginx

2. 挂载配置文件

docker run -d \
  --name nginx \
  -p 80:80 \
  -v /data/www:/usr/share/nginx/html \
  -v /data/nginx/nginx.conf:/etc/nginx/nginx.conf \
  -v /data/nginx/conf.d:/etc/nginx/conf.d \
  nginx

3. 改配置

直接在宿主机改 /data/nginx/conf.d/default.conf,改完重载:

docker exec nginx nginx -s reload

四、挂载日志

docker run -d \
  --name nginx \
  -p 80:80 \
  -v /data/www:/usr/share/nginx/html \
  -v /data/nginx/logs:/var/log/nginx \
  nginx

日志直接在宿主机看:

tail -f /data/nginx/logs/access.log

五、Docker Compose完整配置

version: '3'
services:
  nginx:
    image: nginx:alpine
    container_name: nginx
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /data/www:/usr/share/nginx/html
      - /data/nginx/nginx.conf:/etc/nginx/nginx.conf
      - /data/nginx/conf.d:/etc/nginx/conf.d
      - /data/nginx/logs:/var/log/nginx

启动:

docker-compose up -d

六、常用操作

重载配置

改了配置文件后:

docker exec nginx nginx -s reload

查看日志

docker logs nginx
# 实时看
docker logs -f nginx

进容器

docker exec -it nginx sh

七、常见坑

坑1:挂载后网站403

原因: 宿主机目录权限不对。

解决:

chmod -R 755 /data/www
chown -R nginx:nginx /data/www

坑2:改了配置不生效

原因: 没重载配置。

解决:

docker exec nginx nginx -s reload

坑3:挂载文件不存在,变成目录

-v /data/nginx.conf:/etc/nginx/nginx.conf

如果 /data/nginx.conf 不存在,Docker会自动建一个叫nginx.conf的目录。

解决: 先创建文件:

touch /data/nginx.conf

坑4:端口被占用

80端口被宿主机的Nginx占了。

解决: 停掉宿主机的Nginx,或者换端口。

坑5:改了代码不生效

如果用了CDN或者浏览器缓存,强刷一下(Ctrl+F5)。

八、多站点配置

在 /data/nginx/conf.d/ 下建多个配置文件:

/data/nginx/conf.d/
├── site1.conf
├── site2.conf
└── default.conf

site1.conf:

server {
    listen 80;
    server_name site1.com;
    root /data/www/site1;
    index index.html;
}

改完重载:

docker exec nginx nginx -s reload

九、HTTPS配置

挂载证书:

docker run -d \
  --name nginx \
  -p 80:80 \
  -p 443:443 \
  -v /data/www:/usr/share/nginx/html \
  -v /data/nginx/conf.d:/etc/nginx/conf.d \
  -v /data/ssl:/etc/nginx/ssl \
  nginx

配置文件里引用证书:

server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.pem;
    root /data/www;
}

总结

记住:

  1. 挂载网页目录:-v /data/www:/usr/share/nginx/html
  2. 挂载配置文件:-v /data/nginx/nginx.conf:/etc/nginx/nginx.conf
  3. 挂载日志:-v /data/nginx/logs:/var/log/nginx
  4. 改完配置重载:docker exec nginx nginx -s reload

最常用:

docker run -d --name nginx -p 80:80 \
  -v /data/www:/usr/share/nginx/html \
  -v /data/nginx/conf.d:/etc/nginx/conf.d \
  nginx

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-8 08:25 

Docker运行MySQL,数据持久化配置

前言

用Docker跑MySQL,删了容器数据就没了?那是没挂数据卷。本文讲怎么用Docker跑MySQL并持久化数据。

一、基本运行

最简单的跑法

docker run -d \
  --name mysql \
  -e MYSQL_ROOT_PASSWORD=123456 \
  mysql:5.7

问题: 删了容器,数据库里的数据全没了。

二、加数据卷持久化

完整命令

docker run -d \
  --name mysql \
  -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -v /data/mysql:/var/lib/mysql \
  mysql:5.7

解释

  • -p 3306:3306 端口映射
  • -e MYSQL_ROOT_PASSWORD=123456 设置root密码
  • -v /data/mysql:/var/lib/mysql 数据卷挂载
    • 宿主机 /data/mysql 目录
    • 容器里 /var/lib/mysql 目录

这样容器删了,数据还在宿主机上。

三、其他常用配置

1. 设置字符集

docker run -d \
  --name mysql \
  -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -v /data/mysql:/var/lib/mysql \
  -v /data/mysql/my.cnf:/etc/mysql/conf.d/my.cnf \
  mysql:5.7 \
  --character-set-server=utf8mb4 \
  --collation-server=utf8mb4_unicode_ci

2. 创建数据库和用户

docker run -d \
  --name mysql \
  -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -e MYSQL_DATABASE=mydb \
  -e MYSQL_USER=myuser \
  -e MYSQL_PASSWORD=123456 \
  -v /data/mysql:/var/lib/mysql \
  mysql:5.7

3. 挂载配置文件

先在宿主机创建my.cnf:

[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
max_connections=1000

然后挂载:

docker run -d \
  --name mysql \
  -v /data/mysql/my.cnf:/etc/mysql/conf.d/my.cnf \
  -v /data/mysql:/var/lib/mysql \
  mysql:5.7

四、Docker Compose方式

version: '3'
services:
  mysql:
    image: mysql:5.7
    container_name: mysql
    restart: always
    ports:
      - "3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: mydb
    volumes:
      - /data/mysql:/var/lib/mysql
      - /data/mysql/my.cnf:/etc/mysql/conf.d/my.cnf

启动:

docker-compose up -d

五、连接MySQL

进入容器连接

docker exec -it mysql mysql -uroot -p123456

从宿主机连接

mysql -h127.0.0.1 -P3306 -uroot -p123456

用Navicat等工具

  • 主机:服务器IP
  • 端口:3306
  • 用户名:root
  • 密码:123456

六、备份和恢复

备份数据库

docker exec mysql mysqldump -uroot -p123456 mydb > /data/backup/mydb.sql

恢复数据库

docker exec -i mysql mysql -uroot -p123456 mydb < /data/backup/mydb.sql

七、常见坑

坑1:密码不对

报错:

Access denied for user 'root'@'localhost'

检查: 环境变量 MYSQL_ROOT_PASSWORD 设了没?

坑2:权限问题

宿主机 /data/mysql 目录权限不对:

chmod -R 777 /data/mysql

坑3:端口被占用

3306端口被宿主机的MySQL占了:

Bind for 0.0.0.0:3306 failed: port is already allocated

解决: 换个端口:

-p 3307:3306

坑4:数据没持久化

没加 -v 挂载,删了容器数据就没了。

检查: 有没有 -v /data/mysql:/var/lib/mysql?

坑5:第一次启动慢

MySQL第一次启动要初始化数据,要等个十几秒。

等不及就先别连,过一会再试。

坑6:字符集不对,中文乱码

加参数:

--character-set-server=utf8mb4 \
--collation-server=utf8mb4_unicode_ci

八、推荐配置

docker run -d \
  --name mysql \
  --restart=always \
  -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=你的密码 \
  -v /data/mysql:/var/lib/mysql \
  -v /data/mysql/my.cnf:/etc/mysql/conf.d/my.cnf \
  mysql:5.7
  • --restart=always 开机自动启动
  • 数据挂载到 /data/mysql
  • 配置文件挂载

总结

记住:

  1. 一定要挂数据卷,不然删容器数据就没了
  2. 用 -e MYSQL_ROOT_PASSWORD 设密码
  3. 用 -p 3306:3306 映射端口
  4. 配置文件也可以挂载进去

最常用:

docker run -d --name mysql -p 3306:3306 \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -v /data/mysql:/var/lib/mysql \
  mysql:5.7

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-8 08:24 

Dockerfile编写教程,构建自己的镜像

前言

想用Docker跑自己的项目,不能每次都手动装环境吧?写个Dockerfile,一键构建镜像。本文讲怎么写Dockerfile。

一、什么是Dockerfile

Dockerfile就是一个文本文件,里面写了怎么构建镜像的步骤。

就像写脚本一样,告诉Docker:

  1. 基于哪个基础镜像
  2. 装什么软件
  3. 复制什么文件
  4. 启动什么命令

二、基本结构

# 基于哪个镜像
FROM nginx:alpine

# 维护者信息
MAINTAINER yourname <email@xx.com>

# 工作目录
WORKDIR /usr/share/nginx/html

# 复制文件
COPY index.html .

# 暴露端口
EXPOSE 80

# 启动命令
CMD ["nginx", "-g", "daemon off;"]

三、常用指令

FROM:基础镜像

FROM nginx:alpine
FROM php:7.4-fpm
FROM mysql:5.7

WORKDIR:工作目录

WORKDIR /app

后面的命令都在这个目录里执行。

COPY:复制文件

COPY index.html /usr/share/nginx/html/
COPY ./src /app

RUN:构建时执行命令

RUN apt-get update && apt-get install -y vim

构建镜像的时候执行。

CMD:容器启动时执行

CMD ["nginx", "-g", "daemon off;"]

容器启动后跑的命令。

EXPOSE:暴露端口

EXPOSE 80
EXPOSE 443

只是声明,实际用的时候还要用-p映射。

ENV:设置环境变量

ENV MYSQL_ROOT_PASSWORD=123456
ENV APP_ENV=production

VOLUME:数据卷

VOLUME ["/var/lib/mysql"]

四、实战例子

例子1:部署静态网站

FROM nginx:alpine

WORKDIR /usr/share/nginx/html

COPY index.html .
COPY about.html .

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

例子2:部署PHP项目

FROM php:7.4-fpm

WORKDIR /var/www/html

COPY . .

RUN docker-php-ext-install mysqli pdo pdo_mysql

EXPOSE 9000

CMD ["php-fpm"]

例子3:Python应用

FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt

COPY . .

EXPOSE 5000

CMD ["python", "app.py"]

五、构建镜像

构建命令

docker build -t mynginx:v1 .
  • -t 给镜像起名字和标签
  • . 当前目录找Dockerfile

查看构建过程

docker build --progress=plain -t mynginx:v1 .

指定Dockerfile路径

docker build -f docker/Dockerfile -t mynginx:v1 .

六、常用优化技巧

1. 用小镜像

用alpine版本,小很多:

FROM nginx:alpine

2. 合并RUN命令

RUN apt-get update && \
    apt-get install -y vim wget && \
    rm -rf /var/lib/apt/lists/*

不要写多个RUN,合并成一个,减少层数。

3. .dockerignore文件

不想复制的文件写在.dockerignore里:

.git
node_modules
*.log

4. 多层缓存

把不变的放前面,变的放后面。

# 先复制requirements.txt,装依赖(这个不常变)
COPY requirements.txt .
RUN pip install -r requirements.txt

# 再复制代码(这个常变)
COPY . .

七、常见坑

坑1:Dockerfile文件名不对

必须叫 Dockerfile,首字母大写,没有后缀。

坑2:构建时找不到文件

docker build -t mynginx:v1 .

最后那个点不能少,是构建上下文。

坑3:COPY路径不对

COPY index.html .

这个路径是相对于构建上下文的,不是Dockerfile所在目录。

坑4:CMD和RUN搞混

  • RUN 是构建镜像的时候跑
  • CMD 是容器启动的时候跑

坑5:镜像太大

用alpine版本,或者多阶段构建。

八、多阶段构建

# 构建阶段
FROM node:16 AS build
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
RUN npm run build

# 生产阶段
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80

这样最终镜像很小,只有编译后的文件。

总结

记住:

  1. FROM 基础镜像
  2. WORKDIR 工作目录
  3. COPY 复制文件
  4. RUN 构建时执行
  5. CMD 启动时执行
  6. docker build -t 名字 . 构建镜像

最常用:

FROM nginx:alpine
COPY index.html /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-8 08:23 

Docker查看容器资源占用,stats命令

前言

容器跑多了,哪个占CPU、哪个占内存,一眼看出来。本文讲Docker stats命令。

一、基本用法

查看所有运行中的容器

docker stats

输出:

CONTAINER ID   NAME       CPU %   MEM USAGE / LIMIT   MEM %   NET I/O   BLOCK I/O   PIDS
a1b2c3d4e5f6   mysql      2.50%   500MiB / 2GiB       25.0%   1MB/2MB   10MB/5MB    30
f7e8d9c0b1a2   nginx      0.10%   20MiB / 2GiB        1.0%    5MB/3MB   1MB/0B      5

列解释

  • CPU %:CPU使用率
  • MEM USAGE / LIMIT:内存使用 / 内存限制
  • MEM %:内存使用率
  • NET I/O:网络收发流量
  • BLOCK I/O:磁盘读写
  • PIDS:进程数

二、查看单个容器

docker stats 容器名

只看某一个容器。

三、只看一次,不刷新

默认是实时刷新的,按Ctrl+C退出。

只看一次:

docker stats --no-stream

四、自定义格式

只看CPU和内存

docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"

JSON格式输出

docker stats --format json --no-stream

五、其他常用命令

查看容器占用磁盘

docker system df

查看单个容器磁盘占用

docker ps -s

看SIZE列,就是容器写的磁盘大小。

查看容器网络IO

docker stats --no-stream

看NET I/O列。

六、常见场景

场景1:哪个容器占CPU高?

docker stats

看CPU %列,高的那个就是。

场景2:哪个容器占内存高?

看MEM USAGE列。

场景3:某个容器网络流量大

看NET I/O列。

七、限制容器资源

跑的时候可以限制CPU和内存:

限制内存512M

docker run -d --memory 512m nginx

限制CPU 0.5核

docker run -d --cpus 0.5 nginx

同时限制

docker run -d \
  --memory 512m \
  --cpus 0.5 \
  nginx

八、常见坑

坑1:stats一直刷新,退不出来

按Ctrl+C退出。

坑2:看不准CPU使用率

Docker stats显示的是CPU使用率百分比,多核的话可能超过100%(比如4核跑满就是400%)。

坑3:内存看的是驻留内存

MEM USAGE是容器实际用的内存,包括缓存。

坑4:容器挂了,stats看不到了

docker stats

只看运行中的容器,停止的看不到。

总结

记住:

  1. docker stats 看所有运行中的容器资源占用
  2. 看CPU、内存、网络、磁盘IO
  3. --no-stream 只看一次
  4. 跑容器时可以限制资源

最常用:

docker stats

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-8 08:22 

Docker删除所有停止的容器,一键清理

前言

用Docker久了,一堆停止的容器占磁盘,一个个删太麻烦。本文讲怎么一键清理。

一、查看所有停止的容器

先看看有多少个停止的容器:

docker ps -a

看STATUS列,显示Exited的就是停止的。

只看停止的:

docker ps -a -f status=exited

二、删除所有停止的容器

一键清理

docker container prune

输出:

Total reclaimed space: 1.2GB

直接删掉所有停止的容器。

确认提示

默认会问你:

WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N]

输入y确认。

跳过确认

docker container prune -f

加个 -f 直接删,不问。

三、其他清理命令

删除所有没用的镜像

docker image prune

只删悬空镜像(dangling images),就是没有标签的。

删所有没用的镜像(包括没在用的)

docker image prune -a

清理所有没用的东西

docker system prune

包括:

  • 停止的容器
  • 没用的网络
  • 悬空镜像
  • 构建缓存

清理所有没用的东西(包括镜像)

docker system prune -a

连没在用的镜像也删了。

清理数据卷

docker volume prune

注意: 这个会删没用的数据卷,里面的数据也没了!

四、手动删除单个容器

如果只想删某一个:

docker rm 容器ID或名字

强制删除运行中的容器:

docker rm -f 容器ID或名字

五、批量删除停止的容器

方法1:用docker rm

docker rm $(docker ps -a -q -f status=exited)

解释:

  • docker ps -a -q 列出所有容器ID
  • -f status=exited 只筛停止的
  • docker rm 删掉

方法2:用docker container prune

docker container prune -f

推荐用这个,简单。

六、查看磁盘占用

清理之前看看占了多少:

docker system df

输出:

TYPE            TOTAL   ACTIVE  SIZE    RECLAIMABLE
Images          20      5       10GB    7GB (70%)
Containers      15      5       500MB   400MB (80%)
Local Volumes   8       3       2GB     1GB (50%)
Build Cache     -       -       1GB     800MB

看RECLAIMABLE列,就是可以回收的。

七、常见场景

场景1:测试完一堆容器,想全清了

docker container prune -f

场景2:磁盘满了,全面清理

docker system prune -a -f --volumes
  • -a 删所有没用的镜像
  • -f 不问
  • --volumes 连数据卷也删

注意: 这个很猛,数据卷也删了,重要数据先备份!

场景3:只清理镜像,不动容器

docker image prune -a -f

八、常见坑

坑1:删了数据卷,数据没了

docker system prune --volumes

加了 --volumes 会删没用的数据卷。

注意: 数据库的数据卷别删!

坑2:删了镜像,重新拉要很久

docker image prune -a

把所有没在用的镜像都删了,下次用还要重新拉。

建议: 常用镜像留着,别全删。

坑3:误删了运行中的容器?

docker container prune 只删停止的,运行中的删不了。

放心,安全。

坑4:删了容器,数据卷还在

docker rm 容器名

只删容器,数据卷还在。

想连数据卷一起删:

docker rm -v 容器名

九、定期清理脚本

写个脚本,定期清理:

#!/bin/bash
# 清理停止的容器
docker container prune -f
# 清理悬空镜像
docker image prune -f
# 清理没用的网络
docker network prune -f

加到crontab,每周跑一次:

0 3 * * 0 /home/clean-docker.sh

总结

记住:

  1. docker container prune 删所有停止的容器
  2. docker image prune -a 删所有没用的镜像
  3. docker system prune 全面清理
  4. 删数据卷要小心,别把数据库删了

最常用:

docker container prune -f

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-8 08:21 

Docker数据卷挂载,容器数据持久化

前言

删了Docker容器,数据全没了?那是因为你没挂数据卷。本文讲怎么用数据卷让容器数据持久化。

一、为什么需要数据卷

容器里的数据,容器删了就没了。

docker run -d --name mysql mysql:5.7
# 删了容器,数据就没了
docker rm -f mysql

解决: 把数据存到宿主机,容器删了数据还在。

二、三种数据持久化方式

1. 绑定挂载(bind mount)

把宿主机的目录直接挂到容器里。

docker run -v /宿主机路径:/容器路径 镜像名

例子:

docker run -d \
  -v /data/mysql:/var/lib/mysql \
  mysql:5.7

特点: 简单直接,数据在你自己的目录里。

2. 命名卷(named volume)

Docker管理的数据卷。

docker volume create mydata
docker run -v mydata:/var/lib/mysql mysql:5.7

特点: Docker帮你管理位置,不用你关心存在哪。

3. 临时挂载(tmpfs)

存在内存里,重启就没。

docker run --tmpfs /tmp 镜像名

特点: 临时用,存敏感数据。

三、常用场景

场景1:MySQL数据持久化

docker run -d \
  --name mysql \
  -v /data/mysql:/var/lib/mysql \
  -e MYSQL_ROOT_PASSWORD=123456 \
  mysql:5.7

场景2:Nginx挂载网站文件

docker run -d \
  --name nginx \
  -v /data/www:/usr/share/nginx/html \
  -p 80:80 \
  nginx

场景3:挂载配置文件

docker run -d \
  -v /data/nginx/nginx.conf:/etc/nginx/nginx.conf \
  nginx

场景4:挂载日志

docker run -d \
  -v /data/logs/nginx:/var/log/nginx \
  nginx

四、Docker Compose里的挂载

services:
  mysql:
    image: mysql:5.7
    volumes:
      - /data/mysql:/var/lib/mysql
      # 或者用命名卷
      - mysql_data:/var/lib/mysql

volumes:
  mysql_data:

五、常用命令

查看所有数据卷

docker volume ls

查看数据卷详情

docker volume inspect 数据卷名

删除没用的数据卷

docker volume prune

查看容器挂载

docker inspect -f '{{.Mounts}}' 容器名

六、只读挂载

只想让容器读,不让写:

docker run -v /data/www:/usr/share/nginx/html:ro nginx

后面加个 :ro 就是只读。

七、常见坑

坑1:改了配置文件不生效

挂了配置文件,改了宿主机的文件,重启容器还是旧的。

原因: 容器没真正重启。

解决:

docker restart 容器名

坑2:权限问题

挂载的目录,容器里的用户读不了。

原因: 宿主机目录权限不对。

解决:

chmod -R 777 /data/mysql

坑3:Windows/Mac路径问题

Windows路径写法不一样:

docker run -v D:\data:/var/lib/mysql mysql

Mac/Linux用:

docker run -v /Users/xxx/data:/var/lib/mysql mysql

坑4:挂载文件不存在

宿主机的文件不存在,Docker会自动建一个目录。

比如:

docker run -v /data/nginx.conf:/etc/nginx/nginx.conf nginx

如果 /data/nginx.conf 不存在,Docker会建一个叫 nginx.conf 的目录,容器里就挂错了。

解决: 先创建文件:

touch /data/nginx.conf

坑5:数据卷删了数据就没了

docker volume rm mydata

数据卷删了,里面的数据也没了。

解决: 重要数据定期备份。

八、最佳实践

  1. 数据库一定要挂载数据卷,不然容器删了数据就没了
  2. 配置文件挂载到宿主机,改起来方便
  3. 日志挂载到宿主机,方便查看和切割
  4. 网站文件挂载到宿主机,更新代码不用重新打镜像

总结

记住:

  1. -v 宿主机路径:容器路径 就是挂载
  2. 数据库一定要挂数据卷
  3. 删容器不删数据,因为数据在宿主机
  4. 挂载文件不存在会变成目录,要注意

最常用:

docker run -v /data/mysql:/var/lib/mysql mysql

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-8 08:19 

Docker端口映射,-p参数用法详解

前言

容器跑起来了,但外部访问不了,就是端口没映射好。本文讲Docker端口映射的各种用法。

一、什么是端口映射

Docker容器有自己的内部网络,外部要访问容器里的服务,得把宿主机的端口映射到容器的端口。

二、基本用法

格式

-p 宿主机端口:容器端口

例子

docker run -d -p 8080:80 nginx

意思:访问宿主机的8080端口,转发到容器的80端口。

浏览器访问

http://服务器IP:8080

三、各种写法

1. 指定宿主机端口和容器端口

-p 8080:80

2. 只指定容器端口,宿主机随机分配

docker run -d -P nginx

大写P,宿主机随机找个空闲端口。

3. 绑定指定IP

-p 127.0.0.1:8080:80

只允许本机访问,外网访问不了。

4. UDP端口

-p 53:53/udp

5. 映射多个端口

docker run -d \
  -p 80:80 \
  -p 443:443 \
  -p 2222:22 \
  nginx

四、查看端口映射

查看容器端口

docker port 容器名

输出:

80/tcp -> 0.0.0.0:8080

查看详细信息

docker ps

看PORTS列。

五、Docker Compose里的端口映射

services:
  nginx:
    image: nginx
    ports:
      - "80:80"
      - "443:443"
      - "127.0.0.1:8080:80"

六、常见场景

场景1:跑Nginx

docker run -d -p 80:80 --name mynginx nginx

场景2:跑MySQL

docker run -d -p 3306:3306 --name mysql \
  -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7

场景3:跑Redis

docker run -d -p 6379:6379 --name redis redis

场景4:多个容器跑同一个端口

比如跑两个Nginx,分别用不同端口:

docker run -d -p 8081:80 --name nginx1 nginx
docker run -d -p 8082:80 --name nginx2 nginx

七、端口被占用怎么办

报错

Bind for 0.0.0.0:80 failed: port is already allocated

查谁占用了80端口

netstat -tlnp | grep :80
# 或者
lsof -i:80

解决

  1. 换个端口:-p 8080:80
  2. 或者把占用端口的程序关掉

八、端口映射的坑

坑1:只映射了容器端口,没映射宿主机端口

docker run -d -p 80 nginx

这样宿主机随机分配端口,不好记。

坑2:防火墙没开

端口映射了,但防火墙挡了,外网还是访问不了。

解决: 开放端口:

firewall-cmd --add-port=8080/tcp --permanent
firewall-cmd --reload

坑3:改不了端口映射

容器启动后,想改端口映射,改不了。

解决: 删掉容器重新跑。容器数据用卷挂载了就不怕丢。

坑4:绑定127.0.0.1后外网访问不了

-p 127.0.0.1:8080:80

这个只能本机访问,外网访问不了。

原因: 故意绑的本机。

解决: 改成 0.0.0.0:8080:80 或者直接 8080:80。

总结

记住:

  1. 基本格式:-p 宿主机端口:容器端口
  2. 多个端口就写多个 -p
  3. 端口被占用就换一个
  4. 防火墙也要开

常用:

docker run -d -p 80:80 --name nginx nginx

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-8 08:18 

Docker进入容器内部,exec和attach区别

前言

要进容器里改配置、看日志,用什么命令?docker exec和docker attach有什么区别?本文讲清楚。

一、docker exec(推荐)

作用

在运行中的容器里开一个新进程。

进入容器

docker exec -it 容器名 /bin/bash

或者:

docker exec -it 容器名 sh

参数说明

参数 说明
-i 保持交互
-t 分配终端
-it 合起来用

退出

输入 exit 或者按 Ctrl+D。

关键: 退出后容器还在运行,不受影响。

执行单个命令

不进交互模式,直接执行命令:

docker exec 容器名 ls /var/www/html
docker exec 容器名 cat /etc/nginx/nginx.conf
docker exec 容器名 php -v

二、docker attach

作用

连接到容器正在运行的主进程。

用法

docker attach 容器名

危险!

退出会导致容器停止!

按 Ctrl+C 退出后,容器就停了。

怎么安全退出

按 Ctrl+P 然后 Ctrl+Q,这样退出不会停容器。

三、exec和attach区别

对比 exec attach
原理 开新进程 连接主进程
退出后容器 继续运行 会停止
常用场景 进容器操作 看主进程输出
推荐度 推荐 不推荐

记住: 日常用exec,别用attach。

四、常见场景

场景1:进容器改配置

docker exec -it mynginx /bin/bash

场景2:在容器里执行PHP命令

docker exec myphp php artisan migrate

场景3:看容器里的日志文件

docker exec mynginx tail -f /var/log/nginx/access.log

场景4:容器里没bash

有些精简镜像没有bash,用sh:

docker exec -it 容器名 sh

场景5:以root用户进入

docker exec -u root -it 容器名 /bin/bash

五、如果容器没bash怎么办

用nsenter

# 先拿容器PID
PID=$(docker inspect --format "{{.State.Pid}}" 容器名)

# 进入
nsenter --target $PID --mount --uts --ipc --net --pid

或者用docker run

用同一个镜像开个交互式容器:

docker run -it --rm 镜像名 /bin/bash

六、常用命令汇总

命令 说明
docker exec -it 容器名 /bin/bash 进容器
docker exec 容器名 命令 执行命令
docker exec -u root -it 容器名 /bin/bash 以root进
docker attach 容器名 连接主进程(慎用)

常见坑

坑1:attach退出后容器停了

用attach进去,按Ctrl+C退出,容器就停了。

解决: 用exec,或者Ctrl+P Ctrl+Q退出。

坑2:进容器报/ bin/bash not found

镜像里没有bash。

解决: 用sh:

docker exec -it 容器名 sh

坑3:进容器报permission denied

不是root用户,权限不够。

解决: 加 -u root:

docker exec -u root -it 容器名 /bin/bash

坑4:exec进不去,说容器没运行

容器停了,exec进不去。

解决: 先启动容器:

docker start 容器名
docker exec -it 容器名 /bin/bash

坑5:多个终端连同一个容器

exec可以开多个终端,互不影响。

这是exec比attach好的地方,attach只能连一个。

总结

记住两点:

  1. 日常用 docker exec -it 容器名 /bin/bash
  2. 别用attach,退出会把容器搞停

补充: 精简镜像没有bash,用sh代替。

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-8 08:17 

Docker查看容器IP,docker inspect命令

前言

容器之间要互相通信,得知道容器的IP。本文讲怎么用docker inspect查看容器IP和详细信息。

一、查看容器IP

最简单的方法

docker inspect 容器名

输出一大串JSON,找IPAddress字段。

只看IP

docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' 容器名

直接输出IP地址,比如:172.17.0.2

查看所有运行中容器的IP

docker ps -q | xargs docker inspect -f '{{.Name}} - {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'

输出:

/mynginx - 172.17.0.2
/mymysql - 172.17.0.3
/myphp - 172.17.0.4

二、docker inspect常用用法

查看容器挂载

docker inspect -f '{{.Mounts}}' 容器名

查看容器启动命令

docker inspect -f '{{.Config.Cmd}}' 容器名

查看容器环境变量

docker inspect -f '{{.Config.Env}}' 容器名

查看容器端口映射

docker inspect -f '{{.NetworkSettings.Ports}}' 容器名

查看容器网络

docker inspect -f '{{.NetworkSettings.Networks}}' 容器名

查看容器状态

docker inspect -f '{{.State.Status}}' 容器名

查看容器启动时间

docker inspect -f '{{.State.StartedAt}}' 容器名

查看容器日志路径

docker inspect -f '{{.LogPath}}' 容器名

三、查看镜像信息

查看镜像详细信息

docker inspect 镜像名

查看镜像作者

docker inspect -f '{{.Author}}' nginx

查看镜像默认启动命令

docker inspect -f '{{.Config.Cmd}}' nginx

四、容器之间怎么通信

用容器名通信(推荐)

Docker默认网络里,容器可以用容器名互相访问:

# 在nginx容器里ping mysql
docker exec mynginx ping mysql

不用记IP,直接用容器名就行。

用自定义网络

# 创建网络
docker network create mynet

# 容器加入网络
docker run --name mysql --network mynet -d mysql:5.7
docker run --name nginx --network mynet -d nginx

五、常见场景

场景1:Nginx反向代理连PHP

nginx配置里:

fastcgi_pass php:9000;

这里的php就是容器名,Docker会自动解析成容器IP。

场景2:PHP连MySQL

<?php
$pdo = new PDO('mysql:host=mysql;dbname=mydb', 'root', '123456');
?>

host填mysql,就是MySQL容器的名字。

六、容器IP会变吗?

会的。

容器重启后,IP可能会变。所以不要硬编码IP,用容器名通信最稳。

如果要固定IP: 用自定义网络,指定静态IP。

# docker-compose.yml
services:
  mysql:
    networks:
      mynet:
        ipv4_address: 172.20.0.10

常见坑

坑1:inspect输出太多,找不到IP

解决: 用 -f 参数过滤,只看你要的字段。

坑2:容器停了就看不到IP

容器停止状态下,IPAddress是空的。

解决: 先启动容器:docker start 容器名

坑3:不同网络的容器互相ping不通

原因: 容器在不同网络里。

解决: 把它们加到同一个网络:

docker network connect mynet 容器名

坑4:用容器名ping不通

原因: 不是默认网络,或者Docker版本太老。

解决: 用自定义网络,或者直接用IP。

坑5:host网络模式看不到IP

用 --network=host 启动的容器,IP和宿主机一样。

解决: 不用查了,就是宿主机的IP。

总结

常用命令:

命令 说明
docker inspect 容器名 看容器全部信息
docker inspect -f '{{.NetworkSettings.IPAddress}}' 容器名 只看IP
docker inspect -f '{{.Mounts}}' 容器名 看挂载
docker inspect -f '{{.Config.Env}}' 容器名 看环境变量

记住: 容器之间用容器名通信,别硬编码IP,重启就变了。

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-8 08:15 

Docker容器内时间和宿主机不一致修复

前言

Docker容器里的时间和宿主机差了8个小时,日志时间不对,定时任务也跑偏。本文讲怎么修复容器时区问题。

一、问题现象

# 宿主机时间
date
# 输出:Thu Oct  8 08:14:01 CST 2026

# 容器里时间
docker exec 容器名 date
# 输出:Thu Oct  8 00:14:01 UTC 2026

差了8个小时,因为容器默认用UTC时区。

二、查看容器时区

docker exec 容器名 date

或者:

docker exec 容器名 cat /etc/timezone

三、修复方法

方法1:挂载时区文件(推荐)

启动容器时挂载宿主机的时区文件:

docker run -v /etc/localtime:/etc/localtime:ro 镜像名

这样容器就和宿主机用同一个时区了。

方法2:设置环境变量

docker run -e TZ=Asia/Shanghai 镜像名

方法3:进容器手动改

# 进容器
docker exec -it 容器名 bash

# 装时区数据
apt-get update && apt-get install -y tzdata

# 设置时区
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
echo "Asia/Shanghai" > /etc/timezone

# 验证
date

方法4:Docker Compose设置

在docker-compose.yml里加:

services:
  app:
    image: xxx
    environment:
      - TZ=Asia/Shanghai
    volumes:
      - /etc/localtime:/etc/localtime:ro

四、PHP容器额外设置

PHP的时区和系统时区是两回事,还要改php.ini:

[Date]
date.timezone = Asia/Shanghai

或者在代码里设:

<?php
date_default_timezone_set('Asia/Shanghai');
?>

五、MySQL容器额外设置

MySQL的时区也要改:

-- 查看当前时区
SELECT @@global.time_zone;

-- 设置为东八区
SET GLOBAL time_zone = '+8:00';

永久生效改my.cnf:

[mysqld]
default-time-zone = '+8:00'

六、验证

# 宿主机时间
date

# 容器时间
docker exec 容器名 date

两个时间一致就对了。

常见坑

坑1:改了时区不生效

改了时区文件,重启容器又变回UTC了。

原因: 容器重启后文件系统重置了。

解决: 用挂载的方式,不要进容器改。

坑2:只改了系统时区,PHP时区不对

系统时间对了,但PHP date()还是错的。

原因: PHP有自己的时区设置。

解决: 改php.ini的date.timezone。

坑3:日志时间不对

日志里的时间还是UTC。

原因: 程序启动时读的时区,改了时区要重启程序。

解决: 重启容器或重启程序。

坑4:Windows/Mac Docker Desktop

Windows和Mac上Docker Desktop的时区处理不一样。

解决: 直接用TZ环境变量最稳。

坑5:时区列表找不到Asia/Shanghai

原因: 容器里没装tzdata。

解决: 先装tzdata:

apt-get install -y tzdata

总结

最稳的方法:

  1. 挂载 /etc/localtime:/etc/localtime:ro
  2. 加环境变量 TZ=Asia/Shanghai
  3. PHP改 date.timezone
  4. MySQL改 default-time-zone

记住: 时区不一致是Docker最常见的问题之一,部署完第一件事先验证时间。

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-8 08:14 

Docker Compose部署LNMP环境完整示例

前言

手动装Nginx、PHP、MySQL太麻烦,用Docker Compose一键部署LNMP环境,几分钟搞定。

一、项目结构

lnmp/
├── docker-compose.yml
├── nginx/
│   ├── conf.d/
│   │   └── default.conf
│   └── nginx.conf
├── php/
│   └── php.ini
├── mysql/
│   └── my.cnf
└── www/
    └── index.php

二、docker-compose.yml

version: '3'

services:
  nginx:
    image: nginx:latest
    container_name: lnmp_nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./www:/var/www/html
    depends_on:
      - php
    networks:
      - lnmp

  php:
    image: php:fpm
    container_name: lnmp_php
    volumes:
      - ./www:/var/www/html
      - ./php/php.ini:/usr/local/etc/php/php.ini
    networks:
      - lnmp

  mysql:
    image: mysql:5.7
    container_name: lnmp_mysql
    ports:
      - "3306:3306"
    volumes:
      - ./mysql/data:/var/lib/mysql
      - ./mysql/my.cnf:/etc/mysql/conf.d/my.cnf
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: mydb
    networks:
      - lnmp

networks:
  lnmp:
    driver: bridge

三、Nginx配置

nginx/conf.d/default.conf

server {
    listen       80;
    server_name  localhost;
    root   /var/www/html;
    index  index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass   php:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  /var/www/html$fastcgi_script_name;
        include        fastcgi_params;
    }
}

四、PHP配置

php/php.ini

[PHP]
date.timezone = Asia/Shanghai
upload_max_filesize = 20M
post_max_size = 20M
memory_limit = 128M

五、MySQL配置

mysql/my.cnf

[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_general_ci

六、测试文件

www/index.php

<?php
phpinfo();
?>

七、启动

cd lnmp
docker-compose up -d

查看状态

docker-compose ps

访问

http://localhost

能看到phpinfo页面就是成功了。

八、常用命令

命令 说明
docker-compose up -d 启动
docker-compose down 停止并删除容器
docker-compose restart 重启
docker-compose logs -f 看日志
docker-compose exec php bash 进PHP容器
docker-compose exec mysql bash 进MySQL容器

九、连接MySQL

从宿主机连接

mysql -h 127.0.0.1 -P 3306 -u root -p123456

从PHP连接

<?php
$pdo = new PDO('mysql:host=mysql;dbname=mydb', 'root', '123456');
echo "连接成功";
?>

注意:host填mysql,不是localhost,是docker-compose.yml里的服务名。

十、安装PHP扩展

进入PHP容器

docker-compose exec php bash

安装pdo_mysql

docker-php-ext-install pdo_mysql
docker-php-ext-install mysqli

重启PHP

docker-compose restart php

常见坑

坑1:Nginx报502

502 Bad Gateway,原因是PHP容器没启动,或者地址不对。

解决:确认PHP容器在运行,fastcgi_pass填服务名php:9000。

坑2:MySQL启动失败

原因:数据目录权限不对,或者密码没设。

解决:确认MYSQL_ROOT_PASSWORD设了,数据目录权限改成777。

坑3:文件修改不生效

改了www里的文件,浏览器刷新没变。

解决:确认volumes挂载了./www:/var/www/html。

坑4:端口被占用

port is already allocated,原因是宿主机80端口被占了。

解决:改端口,比如改成8080。

坑5:数据丢失

删了容器,MySQL数据没了。

解决:一定要挂载数据卷,./mysql/data:/var/lib/mysql。

总结

部署五步:建项目目录结构,写docker-compose.yml,写配置文件,docker-compose up -d启动,浏览器访问测试。

记住:数据一定要挂载,不然删容器数据就没了。

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-8 08:13 

Docker镜像占用空间过大,清理无用镜像容器

前言

用了一段时间Docker,磁盘满了。docker images一看,一堆旧镜像和停止的容器占着空间。本文讲怎么清理Docker空间。

一、查看Docker占用空间

docker system df

输出:

TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          50        10        15GB      10GB
Containers      30        10        500MB     400MB
Local Volumes   20        5         2GB       1.5GB

RECLAIMABLE 就是可以回收的空间。

二、查看镜像列表

docker images

看REPOSITORY、TAG、SIZE列。

三、清理停止的容器

删除单个容器

docker rm 容器ID/容器名

删除所有停止的容器

docker container prune

会提示确认,输入y。

加-f强制删除

docker container prune -f

不提示直接删。

四、清理无用镜像

删除单个镜像

docker rmi 镜像ID/镜像名

删除所有悬空镜像

悬空镜像就是 <none>:<none> 那些,构建中间产生的。

docker image prune

删除所有未被使用的镜像

docker image prune -a

注意:这会删掉所有没被容器用着的镜像,小心。

五、清理无用卷

查看卷

docker volume ls

删除无用卷

docker volume prune

六、清理无用网络

docker network prune

七、一键全部清理

docker system prune

清理:

  • 停止的容器
  • 无用网络
  • 悬空镜像
  • 构建缓存

连镜像一起删

docker system prune -a

把没被用的镜像也删掉。

加时间过滤

只删24小时前的:

docker system prune -a --filter "until=24h"

八、常见清理场景

场景1:磁盘快满了

# 先看占了多少
docker system df

# 清理停止的容器
docker container prune -f

# 清理悬空镜像
docker image prune -f

# 清理无用卷
docker volume prune -f

场景2:想彻底清理

docker system prune -a -f --volumes

九、手动清理某个镜像

找到镜像ID

docker images

删除

docker rmi 镜像ID

强制删除

如果镜像被停止的容器引用了,先删容器:

docker rm 容器ID
docker rmi 镜像ID

或者强制删:

docker rmi -f 镜像ID

常见坑

坑1:删镜像报错

Error response from daemon: conflict: unable to delete image (must be forced) - image is being used by stopped container

原因: 有停止的容器在用这个镜像。

解决: 先删容器,或者强制删。

坑2:删了镜像,数据还在

镜像删了,但卷还在,数据没丢。

原因: 数据存在卷里,和镜像分开的。

这是好事! 重新起容器挂载同一个卷,数据就回来了。

坑3:docker system prune删多了

把还要用的镜像也删了。

解决: 重要镜像别删,重新pull就行。

坑4:清理完磁盘还是满的

Docker清理了,但磁盘还是100%。

原因: Docker日志文件太大了。

解决: 清理容器日志:

truncate -s 0 /var/lib/docker/containers/*/*-json.log

或者配置日志大小限制。

坑5:构建缓存占空间

docker builder prune

清理构建缓存。

十、定期清理

可以加个定时任务,每周清理一次:

crontab -e

加:

0 3 * * 0 /usr/bin/docker system prune -a -f --filter "until=168h"

每周日凌晨3点清理一周前的。

总结

常用清理命令:

命令 说明
docker system df 看占用空间
docker container prune 清理停止的容器
docker image prune 清理悬空镜像
docker volume prune 清理无用卷
docker system prune -a 一键全清

记住: 定期清理,别等磁盘满了再弄。

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-8 08:10 

Docker容器启动失败,查看容器日志排查错误

前言

Docker容器启动后马上就退出了,不知道为什么。本文讲怎么查看容器日志,排查启动失败的原因。

一、查看容器状态

列出所有容器

docker ps -a

看STATUS列:

  • Up 就是正常运行
  • Exited (1) 就是异常退出了

查看指定容器状态

docker ps -a | grep 容器名

二、查看容器日志

基本用法

docker logs 容器名/容器ID

实时查看日志

docker logs -f 容器名

查看最后100行

docker logs --tail 100 容器名

带时间戳

docker logs -t 容器名

查看最近10分钟的日志

docker logs --since 10m 容器名

三、常见启动失败原因

原因1:程序启动就退出

比如你跑的是个命令行程序,执行完就退出了,容器当然也跟着退。

解决: 前台运行程序,别后台跑。

# 错误:后台运行
CMD service nginx start

# 正确:前台运行
CMD nginx -g "daemon off;"

原因2:配置文件错误

比如nginx配置写错了,启动失败。

日志里会有:

nginx: [emerg] unknown directive "xxx"

解决: 根据日志提示改配置。

原因3:端口被占用

宿主机的端口已经被别的程序占了。

日志里会有:

Error starting userland proxy: listen tcp 0.0.0.0:80: bind: address already in use

解决: 换个端口,或者把占用端口的程序关掉。

# 查谁占用了80端口
netstat -tlnp | grep :80

原因4:挂载的文件不存在

-v挂载的宿主机路径不存在。

日志里会有:

no such file or directory

解决: 先创建宿主机目录。

mkdir -p /data/www

原因5:权限问题

挂载的目录权限不对,程序读不了。

日志里会有:

Permission denied

解决: 改目录权限:

chmod 755 /data/www

原因6:环境变量没设

程序需要的环境变量没传进去。

日志里会有:

Environment variable XXX is not set

解决: 启动时传环境变量:

docker run -e MYSQL_ROOT_PASSWORD=123456 mysql

四、排查步骤

第一步:看状态

docker ps -a

确认容器是Exited状态。

第二步:看日志

docker logs 容器名

日志最后几行就是报错原因。

第三步:进容器看

如果日志不够详细,进容器里手动跑:

docker run -it 镜像名 /bin/bash

进去后手动执行启动命令,看报什么错。

第四步:检查配置

挂载的配置文件对不对,环境变量有没有。

五、常用命令汇总

命令 说明
docker ps -a 看所有容器状态
docker logs 容器名 看日志
docker logs -f 容器名 实时看日志
docker logs --tail 100 容器名 看最后100行
docker inspect 容器名 看容器详细信息
docker exec -it 容器名 /bin/bash 进运行中的容器
docker run -it 镜像 /bin/bash 用镜像开个交互式容器

六、查看容器详细信息

docker inspect 容器名

能看到挂载、环境变量、网络等所有信息。

只看挂载信息

docker inspect -f '{{.Mounts}}' 容器名

只看启动命令

docker inspect -f '{{.Config.Cmd}}' 容器名

常见坑

坑1:容器启动马上退出,日志是空的

docker logs 容器名

什么都没有。

原因: 程序本身就没输出,或者启动太早就挂了。

解决: 用 docker run -it 镜像 /bin/bash 进去手动跑。

坑2:改了配置不生效

改了挂载的配置文件,重启容器还是不生效。

原因: 容器没真正重启,或者配置文件路径不对。

解决: 强制重启:

docker restart 容器名

坑3:日志一直刷,停不下来

docker logs -f 容器名

日志一直在刷。

解决: 按 Ctrl+C 退出。

坑4:容器删除了日志就没了

容器删了,日志也跟着没了。

解决: 重要日志挂载到宿主机。

坑5:看日志找不到容器ID

容器太多,不知道哪个是哪个。

解决: 启动的时候给容器起个好记的名字:

docker run --name mynginx -d nginx

总结

排查三步:

  1. docker ps -a 看容器状态
  2. docker logs 容器名 看报错
  3. 按日志提示改问题

记住: 容器启动失败别慌,看日志就行,日志里都写了原因。

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-8 08:01 

MySQL锁表查询,kill锁表进程

前言

网站突然卡死,查什么都慢,可能是表被锁住了。怎么查谁锁的表,怎么杀掉锁表进程?

一、查看正在执行的进程

SHOW FULL PROCESSLIST;

看State列,要是 Locked 就是锁表了。

二、查看锁表信息

查看当前锁

SELECT * FROM information_schema.INNODB_TRX;

这是InnoDB正在执行的事务。

查看锁等待

SELECT * FROM information_schema.INNODB_LOCK_WAITS;

看谁在等谁的锁。

查看具体的锁

SELECT * FROM information_schema.INNODB_LOCKS;

MySQL 8.0改成了 performance_schema.data_locks。

三、找出锁表的SQL

查看长时间运行的事务

SELECT 
    trx_id,
    trx_state,
    trx_started,
    trx_mysql_thread_id,
    TIMESTAMPDIFF(SECOND, trx_started, NOW()) as run_seconds,
    trx_query
FROM information_schema.INNODB_TRX
ORDER BY trx_started;

run_seconds 越大,跑的越久,越可能是锁表的元凶。

查看锁等待链

SELECT 
    r.trx_id as waiting_trx_id,
    r.trx_mysql_thread_id as waiting_thread,
    r.trx_query as waiting_query,
    b.trx_id as blocking_trx_id,
    b.trx_mysql_thread_id as blocking_thread,
    b.trx_query as blocking_query
FROM information_schema.INNODB_LOCK_WAITS w
JOIN information_schema.INNODB_TRX b ON b.trx_id = w.blocking_trx_id
JOIN information_schema.INNODB_TRX r ON r.trx_id = w.requesting_trx_id;

四、杀掉锁表进程

找到进程ID

从上面的查询结果里,找到 blocking_thread,就是锁表的那个连接ID。

杀掉进程

KILL 进程ID;

批量杀掉

比如杀掉所有跑了超过60秒的事务:

SELECT CONCAT('KILL ', trx_mysql_thread_id, ';') 
FROM information_schema.INNODB_TRX 
WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 60;

把结果复制出来执行。

五、常用查询

查看谁在锁什么表

SELECT 
    t.table_name,
    t.table_rows,
    l.lock_type,
    l.lock_mode,
    l.lock_id,
    t2.trx_mysql_thread_id,
    t2.trx_query,
    t2.trx_started
FROM information_schema.INNODB_LOCKS l
JOIN information_schema.INNODB_TRX t2 ON l.lock_trx_id = t2.trx_id
JOIN information_schema.TABLES t ON l.lock_table = CONCAT(t.table_schema, '/', t.table_name);

查看行锁还是表锁

  • RECORD 是行锁
  • TABLE 是表锁

六、为什么会锁表

原因1:大事务没提交

开了个事务,执行了一大堆,一直没commit,锁就一直占着。

解决: 事务要短小,及时提交。

原因2:没有索引

更新/删除没走索引,会锁全表。

解决: 给where条件加索引。

原因3:DDL操作

ALTER TABLE、OPTIMIZE TABLE这些会锁表。

解决: 低峰期做,或者用pt-online-schema-change。

原因4:长查询

一条SQL跑几分钟,锁住相关行。

解决: 优化慢SQL。

七、MySQL 8.0的变化

MySQL 8.0把INNODB_LOCKS和INNODB_LOCK_WAITS废弃了,改成了performance_schema。

查看锁等待(8.0)

SELECT * FROM performance_schema.data_lock_waits;

查看锁(8.0)

SELECT * FROM performance_schema.data_locks;

常见坑

坑1:杀错进程

杀了正在正常执行的业务进程,导致业务报错。

解决: 先看清楚trx_query是什么,确认是僵尸事务再杀。

坑2:杀了又出来

杀了锁表进程,过一会儿又锁了。

原因: 程序里有死循环,或者连接池自动重连了。

解决: 找到源头,修代码。

坑3:KILL不掉

KILL 123;

进程还在,没杀掉。

原因: 事务在做rollback,需要时间。

解决: 等它回滚完,或者直接重启MySQL。

坑4:查不到锁信息

information_schema.INNODB_TRX是空的。

原因: 可能是MyISAM表,MyISAM的表锁不在InnoDB里。

MyISAM表锁查看:

SHOW OPEN TABLES WHERE In_use > 0;

坑5:锁和MDL搞混

MDL(元数据锁)不是行锁,是表结构的锁。

查看MDL锁:

SELECT * FROM performance_schema.metadata_locks;

总结

锁表排查三步:

  1. SHOW FULL PROCESSLIST 看有没有Locked
  2. information_schema.INNODB_TRX 找长事务
  3. KILL 进程ID 杀掉锁表进程

记住: 杀之前先看清楚是什么SQL,别误杀了正常业务!

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-8 08:00 

MySQL定时备份脚本,自动删除旧备份

前言

数据库数据很重要,必须每天自动备份。写个备份脚本,自动备份+自动删旧的,不用手动管。

一、备份脚本

完整脚本 backup_mysql.sh

#!/bin/bash

# MySQL配置
DB_USER="root"
DB_PASS="your_password"
DB_NAME="mydb"

# 备份目录
BACKUP_DIR="/data/backup/mysql"

# 保留天数
KEEP_DAYS=7

# 日期
DATE=$(date +%Y%m%d_%H%M%S)

# 备份文件名
BACKUP_FILE="$BACKUP_DIR/${DB_NAME}_${DATE}.sql.gz"

# 创建目录
mkdir -p $BACKUP_DIR

# 备份并压缩
mysqldump -u$DB_USER -p$DB_PASS $DB_NAME | gzip > $BACKUP_FILE

# 检查备份是否成功
if [ $? -eq 0 ]; then
    echo "[$(date)] 备份成功: $BACKUP_FILE"
else
    echo "[$(date)] 备份失败!"
    exit 1
fi

# 删除7天前的旧备份
find $BACKUP_DIR -name "*.sql.gz" -mtime +$KEEP_DAYS -delete

echo "[$(date)] 旧备份清理完成"

二、脚本说明

各部分解释

部分 说明
DB_USER 数据库用户名
DB_PASS 数据库密码
DB_NAME 要备份的数据库名
BACKUP_DIR 备份存放目录
KEEP_DAYS 保留几天的备份
mysqldump 备份命令
gzip 压缩,节省空间
find -mtime +7 找7天前的文件删除

备份全部数据库

mysqldump -u$DB_USER -p$DB_PASS --all-databases | gzip > $BACKUP_FILE

备份多个数据库

mysqldump -u$DB_USER -p$DB_PASS --databases db1 db2 db3 | gzip > $BACKUP_FILE

三、设置执行权限

chmod +x /data/backup/backup_mysql.sh

四、测试脚本

/data/backup/backup_mysql.sh

看有没有备份文件生成:

ls -lh /data/backup/mysql/

五、设置定时任务

每天凌晨3点备份

crontab -e

添加:

0 3 * * * /data/backup/backup_mysql.sh >> /data/backup/backup.log 2>&1

说明:

  • 0 3 * * * 每天凌晨3点
  • >> backup.log 输出写到日志
  • 2>&1 错误也写进去

查看定时任务

crontab -l

六、备份文件恢复

恢复步骤

# 解压
gunzip < mydb_20261008_030000.sql.gz | mysql -u root -p mydb

或者先解压再恢复

# 解压
gunzip mydb_20261008_030000.sql.gz

# 恢复
mysql -u root -p mydb < mydb_20261008_030000.sql

七、更完善的脚本

带日志和邮件通知的版本

#!/bin/bash

DB_USER="root"
DB_PASS="your_password"
DB_NAME="mydb"
BACKUP_DIR="/data/backup/mysql"
KEEP_DAYS=7
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/${DB_NAME}_${DATE}.sql.gz"
LOG_FILE="$BACKUP_DIR/backup.log"

mkdir -p $BACKUP_DIR

echo "========== $(date) 开始备份 ==========" >> $LOG_FILE

# 备份
mysqldump -u$DB_USER -p$DB_PASS $DB_NAME 2>>$LOG_FILE | gzip > $BACKUP_FILE

if [ $? -eq 0 ]; then
    FILE_SIZE=$(ls -lh $BACKUP_FILE | awk '{print $5}')
    echo "[$(date)] 备份成功,文件大小: $FILE_SIZE" >> $LOG_FILE
else
    echo "[$(date)] 备份失败!" >> $LOG_FILE
    # 这里可以加邮件通知
    exit 1
fi

# 删除旧备份
find $BACKUP_DIR -name "*.sql.gz" -mtime +$KEEP_DAYS -delete
echo "[$(date)] 清理 $KEEP_DAYS 天前的旧备份完成" >> $LOG_FILE

echo "========== $(date) 备份结束 ==========" >> $LOG_FILE

常见坑

坑1:密码写在脚本里不安全

脚本里明文写了数据库密码。

解决: 用mysql配置文件:

# /root/.my.cnf
[client]
user=root
password=your_password

然后脚本里就不用写密码了:

mysqldump $DB_NAME | gzip > $BACKUP_FILE

设置权限:

chmod 600 /root/.my.cnf

坑2:备份文件太大

不压缩的话备份文件很大。

解决: 用gzip压缩,能压缩到原来的1/10。

坑3:定时任务不执行

cron里写了脚本,但没执行。

原因:

  1. 脚本没加执行权限
  2. 路径写错了
  3. 环境变量不对

解决: 脚本里用全路径,比如 /usr/bin/mysqldump。

坑4:备份完没验证

备份文件损坏了都不知道。

解决: 定期找个测试库恢复一下,看备份能不能用。

坑5:备份文件存在同一台服务器

服务器挂了,备份也没了。

解决: 把备份传到远程服务器或OSS。

# 传到远程服务器
scp $BACKUP_FILE root@192.168.1.20:/data/backup/

总结

备份三步走:

  1. 写备份脚本(mysqldump + gzip + find删旧)
  2. 加执行权限
  3. 加crontab定时任务

记住: 备份不是备份了就完了,要定期验证能不能恢复!

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-8 07:59 

MySQL自增主键不连续,解决办法

前言

删了几条数据,再插入新数据,id跳了一大截,不连续了。怎么让自增主键从1开始?

一、为什么自增主键会不连续

原因1:删除了数据

删了中间的几条,id就跳了。这是正常的,InnoDB的自增计数器不会回退。

原因2:事务回滚

插入一半回滚了,自增id已经用了,不会退回来。

原因3:批量插入部分失败

插100条,第50条失败了,前49条成功,后面的id就跳了。

原因4:TRUNCATE和DELETE的区别

TRUNCATE会重置自增计数器,DELETE不会。

二、查看当前自增值

SHOW TABLE STATUS LIKE 'users';

看 Auto_increment 字段,就是下一条要插入的id。

三、重置自增主键

方法1:修改自增值

ALTER TABLE users AUTO_INCREMENT = 1;

注意: 如果表里已有数据,会设为当前最大id+1。

方法2:清空表后重置

-- 清空表(重置自增)
TRUNCATE TABLE users;

TRUNCATE会把自增计数器重置为1。

方法3:DELETE后手动重置

-- 删除所有数据
DELETE FROM users;

-- 手动重置自增
ALTER TABLE users AUTO_INCREMENT = 1;

四、怎么让自增主键连续

问题:真的需要连续吗?

其实不需要。

自增主键只要唯一就行,不连续不影响业务。用户看到的id跳了也没关系。

如果非要连续

那只能:

  1. 导出数据
  2. 清空表
  3. 重新导入
# 导出数据
mysqldump -u root -p mydb users > users.sql

# 清空表
mysql -u root -p mydb -e "TRUNCATE TABLE users"

# 重新导入
mysql -u root -p mydb < users.sql

注意: 重新导入后id会重新从1开始,但如果表之间有外键关联,会出问题。

五、InnoDB自增的坑

坑1:重启后自增值会变

MySQL 5.7之前,InnoDB把自增值存在内存里,重启后会从当前max(id)+1重新算。

如果删了最大id的那条,重启后自增值就变小了,可能重复。

MySQL 8.0修复了这个问题,把自增值持久化到redo log了。

坑2:批量插入会跳很多id

一次性插入很多数据,中间失败了,id跳了一大截。

解决: 不要批量插,一条条插。

坑3:自增步长

默认步长是1,可以改:

SHOW VARIABLES LIKE 'auto_increment_increment';

一般主从复制时会设不同步长,防止id冲突。

六、常见问题

问题1:自增主键突然跳到很大

突然从100跳到10000,怎么回事?

可能原因:

  1. 批量插入失败了
  2. 有人手动改了AUTO_INCREMENT
  3. 主从同步导致的

问题2:自增主键重复了

ERROR 1062 (23000): Duplicate entry '100' for key 'PRIMARY'

原因: 重启后自增值变小了,和已有数据冲突。

解决: 手动设大一点:

ALTER TABLE users AUTO_INCREMENT = 10000;

问题3:删除数据后id不连续

这是正常现象,不用管。

常见坑

坑1:TRUNCATE和DELETE搞混

DELETE FROM users 不会重置自增。
TRUNCATE TABLE users 会重置自增。

记住: 想重置自增用TRUNCATE。

坑2:外键约束导致TRUNCATE失败

ERROR 1701 (42000): Cannot truncate a table referenced in a foreign key constraint

解决: 先关外键检查:

SET FOREIGN_KEY_CHECKS = 0;
TRUNCATE TABLE users;
SET FOREIGN_KEY_CHECKS = 1;

坑3:重置自增后插入报错

设AUTO_INCREMENT=1,但表里已经有id=1的数据了。

解决: 设成max(id)+1:

SELECT MAX(id) FROM users;
-- 假设max(id)=100
ALTER TABLE users AUTO_INCREMENT = 101;

总结

记住几点:

  1. 自增主键不连续是正常的,不用纠结
  2. TRUNCATE会重置自增,DELETE不会
  3. 重置自增用 ALTER TABLE t AUTO_INCREMENT = n
  4. MySQL 8.0之前重启会丢自增值,要注意

别追求id连续,没意义,唯一就行。

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-8 07:58 

MySQL删除重复数据,保留一条

前言

表里有重复数据,同一个用户存了好几条。怎么删除重复数据,只保留一条?

一、先查有哪些重复数据

按字段查重复

SELECT name, COUNT(*) as cnt 
FROM users 
GROUP BY name 
HAVING cnt > 1;

查重复的完整记录

SELECT * FROM users 
WHERE name IN (
    SELECT name FROM users 
    GROUP BY name 
    HAVING COUNT(*) > 1
)
ORDER BY name;

二、删除重复数据,保留一条

方法1:用自连接(推荐)

DELETE u1 FROM users u1
INNER JOIN users u2
WHERE u1.id > u2.id AND u1.name = u2.name;

意思: 同一个name,id大的删掉,保留id小的。

方法2:用NOT IN

DELETE FROM users 
WHERE id NOT IN (
    SELECT min_id FROM (
        SELECT MIN(id) as min_id 
        FROM users 
        GROUP BY name
    ) as tmp
);

方法3:用临时表

-- 第一步:建临时表存要保留的id
CREATE TABLE tmp AS
SELECT MIN(id) as min_id FROM users GROUP BY name;

-- 第二步:删除不在临时表里的
DELETE FROM users WHERE id NOT IN (SELECT min_id FROM tmp);

-- 第三步:删临时表
DROP TABLE tmp;

三、按多个字段去重

比如name和email都重复才算重复:

DELETE u1 FROM users u1
INNER JOIN users u2
WHERE u1.id > u2.id 
AND u1.name = u2.name 
AND u1.email = u2.email;

四、保留最新的一条

不是保留id最小的,而是保留最新创建的:

DELETE u1 FROM users u1
INNER JOIN users u2
WHERE u1.created_at < u2.created_at 
AND u1.name = u2.name;

五、批量去重前先备份

重要! 删之前先备份:

CREATE TABLE users_bak AS SELECT * FROM users;

确认没问题再删。

六、怎么防止重复数据

加唯一索引

ALTER TABLE users ADD UNIQUE INDEX uk_name (name);

加了唯一索引,重复数据就插不进去了。

插入前先查

// PHP示例
$exists = $db->query("SELECT id FROM users WHERE name = '$name'");
if ($exists) {
    echo "已存在";
} else {
    $db->query("INSERT INTO users ...");
}

用INSERT IGNORE

INSERT IGNORE INTO users (name) VALUES ('张三');

重复了就忽略,不报错。

用ON DUPLICATE KEY UPDATE

INSERT INTO users (name, count) VALUES ('张三', 1)
ON DUPLICATE KEY UPDATE count = count + 1;

重复了就更新,不插入新的。

常见坑

坑1:删完发现数据没了

删重复的时候把正常数据也删了。

原因: 关联条件写错了。

解决: 先备份,再用SELECT确认要删哪些:

SELECT u1.* FROM users u1
INNER JOIN users u2
WHERE u1.id > u2.id AND u1.name = u2.name;

确认没问题再把SELECT改成DELETE。

坑2:大表删太慢

几百万条数据,删半天删不完。

解决: 分批删,每次删1000条:

DELETE FROM users WHERE ... LIMIT 1000;

坑3:自连接删不干净

有的重复数据没删掉。

原因: 字段值有NULL,NULL=NULL是unknown。

解决: 加NULL判断:

DELETE u1 FROM users u1
INNER JOIN users u2
WHERE u1.id > u2.id 
AND (u1.name = u2.name OR (u1.name IS NULL AND u2.name IS NULL));

坑4:报错"You can't specify target table for update in FROM clause"

MySQL不允许先查再删同一张表。

解决: 套一层临时表:

DELETE FROM users 
WHERE id NOT IN (
    SELECT min_id FROM (
        SELECT MIN(id) as min_id FROM users GROUP BY name
    ) as tmp
);

总结

去重三步:

  1. 先查:GROUP BY ... HAVING COUNT(*) > 1
  2. 再确认:SELECT出来看哪些要删
  3. 最后删:自连接DELETE

记住: 删之前务必备份!

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-8 07:57 

MySQL查看慢查询日志,long_query_time参数

前言

网站突然变慢,不知道哪条SQL拖后腿。开慢查询日志,把跑得慢的SQL都抓出来优化。

一、什么是慢查询日志

慢查询日志(slow query log)记录执行时间超过阈值的SQL语句。

作用: 找到慢SQL,优化它们。

二、查看是否开启

SHOW VARIABLES LIKE 'slow_query_log';
Variable_name Value
slow_query_log OFF

OFF就是没开。

三、开启慢查询日志

方法1:临时开启(重启失效)

-- 开启
SET GLOBAL slow_query_log = ON;

-- 设置阈值(超过2秒算慢)
SET GLOBAL long_query_time = 2;

方法2:永久开启

改my.cnf:

[mysqld]
# 开启慢查询
slow_query_log = 1

# 慢查询阈值(秒)
long_query_time = 2

# 慢查询日志文件位置
slow_query_log_file = /var/log/mysql/slow.log

# 没走索引的SQL也记下来(可选)
log_queries_not_using_indexes = 1

重启MySQL:

systemctl restart mysqld

四、查看慢查询日志

直接看文件

tail -f /var/log/mysql/slow.log

用mysqldumpslow分析

MySQL自带的分析工具:

# 按查询时间排序,取前10
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log

常用参数:

参数 说明
-s t 按时间排序
-s c 按次数排序
-s l 按锁时间排序
-t 10 取前10条
-g 过滤关键词

示例:

# 按次数最多的排
mysqldumpslow -s c -t 20 /var/log/mysql/slow.log

# 只看select语句
mysqldumpslow -g "select" /var/log/mysql/slow.log

五、用pt-query-digest分析

更专业的分析工具(需要安装):

pt-query-digest /var/log/mysql/slow.log

输出更详细的统计报告。

六、常用参数说明

参数 默认值 说明
slow_query_log OFF 是否开启
long_query_time 10 慢查询阈值(秒)
slow_query_log_file 主机名-slow.log 日志文件位置
log_queries_not_using_indexes OFF 没走索引的也记
log_output FILE 日志输出方式(FILE/TABLE)

七、优化慢SQL

找到慢SQL后怎么优化:

1. 加索引

EXPLAIN SELECT * FROM users WHERE name = '张三';

看type是不是ALL,是ALL说明没走索引。

2. 避免select *

只查需要的字段:

-- 别这样
SELECT * FROM users;

-- 这样
SELECT id, name FROM users;

3. 避免大表全表扫描

加where条件,加limit。

4. 优化分页

大表分页越查越慢,用游标分页。

常见坑

坑1:long_query_time设太小

设成0.1秒,日志文件爆炸,全是慢SQL。

建议: 生产环境设1-2秒。

坑2:日志文件越来越大

慢查询日志一直写,磁盘满了。

解决: 用logrotate切割日志。

坑3:开启慢查询影响性能

开慢查询本身有性能损耗。

解决: 排查完就关掉,或者用低采样率。

坑4:long_query_time不生效

改了参数,但还是记录所有SQL。

原因: 连接没断开,用的还是旧参数。

解决: 重新连接MySQL。

坑5:log_output=TABLE占满系统表

把慢查询写到mysql.slow_log表,表越来越大。

解决: 定期清理:

TRUNCATE TABLE mysql.slow_log;

总结

开启三步:

  1. SET GLOBAL slow_query_log = ON
  2. SET GLOBAL long_query_time = 2
  3. 看日志:mysqldumpslow -s t -t 10 slow.log

记住: 慢查询日志是优化数据库的第一步,先找到慢SQL再优化。

遇到问题加QQ23979811 协助处理


emer 发布于  2026-10-8 07:55 
'); })();