个人统计

用户名: 没楼可以吗
等级: 初来乍到
威望: 0
在线时间: 4 小时
日志总数: 383
评论数量: 16
访问次数: 232066
建立时间: 2004-04-28
RSS订阅       手机访问

博主资料

留言短消息 加为好友 收藏

用户ID:  41
昵称:  没楼可以吗
来自:  山东 聊城
年龄:  保密

日历

2018 - 12
      1
2345678
9101112131415
16171819202122
23242526272829
3031     
«» 2018 - 12 «»

最近访问

日志分类

圈子信息

好友(0)

首页 前页 后页 尾页
1页,共 0 页

日志文章列表

2018年11月29日 16:00:57

OE removed access to the following unsafe attachments in your mail

OE removed access to the following unsafe attachments in your mail

解决方法:
Tools | Options | Security. Uncheck: "Do not allow attachments................."

类别: 无分类 |  评论(0) |  浏览(17) |  收藏
2018年11月25日 15:21:20

能上网但是显示红叉 win7右下角网络连接显示红叉,但可以正常上网

来自:https://jingyan.baidu.com/article/f79b7cb37a6f2d9144023ed8.html

    今天小编在使用电脑的时候,发现自己的电脑右下角的网络连接选项,已经连接上了无线网络,且能够自动上网,但是网络图标却显示感叹号,有时候现实一个“×”,右键点击网络选择属性,看到查看基本网络信息并设置连接栏目下方显示未知,今天小编就为大家分享下具体的解决办法。
方法/步骤
1右键打开开始菜单, 找到运行选项,在运行文本框输入【services.msc】命令,打开服务框
2进入服务列表,找到“DHCP Client的服务”查看其是否已经启动。
3此时我们在服务列表中找到【Network List Service】服务,此时这个服务多半已经停止,点击启动时候可能会提示我们无法启动。

此时我们打开运行输入【dcomcnfg】点击确定,进入【组件服务】
4进入组件服务,点击计算机,然后点击显示的【我的电脑】
5点击我的电脑选择【DOCM配置】,然后在右方的列表中找到【netprofm】,右键点击选择属性
6进入属性对话框,点击上方菜单栏的安全,选择【启动与激活权限】点击下方的自定义选项,然后点击后面的【编辑】
7在弹出的对话框,组和用户名下方点击【添加】选项
8在【输入对象名称来选择】里输入“LOCAL SERVICE”用户名,点击【检查名称】然后点击确定。
    LOCAL SERVICE账号添加完成后,将其下方的【本地启动】【本地激活】勾选,点击确定

9此时再次回到服务列表,点击启动【Network List Service】就会发现已经可以正常启动了,右下角的网络连接也正常了。

类别: 无分类 |  评论(0) |  浏览(21) |  收藏
2018年11月04日 11:54:36

Certbot 自动化生成 https 证书

Let’s Encrypt 是一个自动签发 https 证书的免费项目
Certbot 是 Let’s Encrypt 官方推荐的证书生成客户端工具

基本操作
0 准备工作
将要签发证书的域名(如 you.domain.com) 解析到一台可在外网正常访问的服务器

1 登录服务器
my-pc$ ssh user@you.domain.com
2 安装 certbot
这里以 Ubuntu 16.04 为例
各个系统的安装方式不一样,可以在官网首页查阅

my-server$ cat /etc/issue
Ubuntu 16.04.2 LTS \n \l

my-server$ sudo apt-get update
my-server$ sudo apt-get install software-properties-common
my-server$ sudo add-apt-repository ppa:certbot/certbot
my-server$ sudo apt-get update
my-server$ sudo apt-get install certbot
my-server$ certbot --version
certbot 0.19.0
3 检查端口占用
如果服务器上的 443 端口正在被占用,请先关闭对应的服务进程
否则可能个导致 certbot 运行出错

4 以命令交互方式开始制作证书
my-server$ certbot certonly    --------> 开启命令

Saving debug log to /var/log/letsencrypt/letsencrypt.log

How would you like to authenticate with the ACME CA?
1: Spin up a temporary webserver (standalone)
2: Place files in webroot directory (webroot)

Select the appropriate number [1-2] then [enter]
(press 'c' to cancel): 1  --------> 这里我们选择 1 standalone 模式
Plugins selected: Authenticator standalone, Installer None
Enter email address (used for urgent renewal and security notices) (Enter 'c' to
cancel): you@gmail.com  --------> 输入你的邮箱创建账号(只有第一次使用时会出现)

Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf. You must agree
in order to register with the ACME server at
https://acme-v01.api.letsencrypt.org/directory
(A)gree/(C)ancel: a  --------> 选择 A 同意 (只有第一次使用时会出现)

Would you be willing to share your email address with the Electronic Frontier
Foundation, a founding partner of the Let's Encrypt project and the non-profit
organization that develops Certbot? We'd like to send you email about EFF and
our work to encrypt the web, protect its users and defend digital rights.
(Y)es/(N)o: y  --------> 选择 Y 或 N (只有第一次使用时会出现)
Please enter in your domain name(s) (comma and/or space separated)  (Enter 'c'
to cancel): you.domain.com  --------> 这里输入要制作证书的域名

Obtaining a new certificate
Performing the following challenges:
tls-sni-01 challenge for you.domain.com
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
  /etc/letsencrypt/live/you.domain.com/fullchain.pem
  Your key file has been saved at:
  /etc/letsencrypt/live/you.domain.com/privkey.pem
  Your cert will expire on 2018-01-26. To obtain a new or tweaked
  version of this certificate in the future, simply run certbot
  again. To non-interactively renew *all* of your certificates, run
  "certbot renew"
- If you like Certbot, please consider supporting our work by:

  Donating to ISRG / Let's Encrypt:  https://letsencrypt.org/donate
  Donating to EFF:                    https://eff.org/donate-le


my-server$ ls /etc/letsencrypt/live/you.domain.com/  --------> 证书文件目录
cert.pem  chain.pem  fullchain.pem  privkey.pem  README 
看到 IMPORTANT NOTES 下方出现 Congratulations 就表明证书已经成功签发
可以进入 /etc/letsencrypt/live/you.domain.com/ 目录查看相关生成的文件

进阶操作
使用 80 端口
默认 standalone 是使用 443 端口,也就是说要停止服务器现在占用 443 端口的进程
我们也可以将其改为使用 80 端口,同样道理,这时需要停止 80 端口的占用

my-server$ certbot certonly --standalone -n --agree-tos --email you@gmail.com --preferred-challenges http -d you.domain.com
-n 非交互式
--email 指定账户
--agree-tos 同意服务协议

使用 nginx 模式
安装 nginx 和插件

my-server$ apt-get install nginx
my-server$ apt-get install python-certbot-nginx
增加 nginx 配置

server {
        server_name you.domain.com;
}
制作证书

my-server$ certbot --nginx -d you.domain.com
使用 webroot 模式
standalone 需要停止服务器,nginx 模式需要修改配置,如果需要在不影响服务器正常运行的情况下制作证书,可以选择 webroot 模式
有个前提,需要你的服务的 80 端口上运行一个能处理静态文件的 web 服务,我们就以 nginx 为例 (注意,webroot + nginx 与 nginx 是完全不同的两种方式)

先确定一个 webroot 目录,这里以 /var/www/html/ 为例

server {
        listen 80 default_server;
        location / {
            alias /var/www/html/;
        }
}
重启 nginx ,制作证书

my-server$ nginx -s reload
my-server$ certbot certonly --webroot -w /var/www/html/ -d you.domain.com
在执行命令后,certbot 会向指定的 webroot 目录中添加一个随机文件,随后 letsencrypt 会通过 http 访问那个文件,比如 http://you.domain.com/.well-known/acme-challenge/gdEMr7ZXOiZE51he9QwuvnbrrTnkwlpFhNAcArBt2uE, 如果能返回正确的数据则通过认证,否则认证失败
所以,即使不使用 nginx,只要能保证你的服务器能正确的返回 webroot 下的文件就行

使用 manual 模式
manual 模式其实和 webroot 类似,主要的区别就是 certbot 自动向 webroot 添加文件的过程,变为你自己手动完成

my-server$ certbot certonly --manual -d you.domain.com

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for you.domain.com

NOTE: The IP of this machine will be publicly logged as having requested this
certificate. If you're running certbot in manual mode on a machine that is not
your server, please ensure you're okay with that.

Are you OK with your IP being logged?
(Y)es/(N)o: y    --------> 选 Y

Create a file containing just this data:

_Pamo3hOMx37O-KHIioWO1P0hbTLZusEZ2a2iCf7PDQ.8CyQk-SfDMR10nk7DuGBFA4IEpVhm1OySU-nSItD1BE

And make it available on your web server at this URL:

http://you.domain.com/.well-known/acme-challenge/_Pamo3hOMx37O-KHIioWO1P0hbTLZusEZ2a2iCf7PDQ

Press Enter to Continue 
--------> 你需要保证 http://you.domain.com/.well-known/acme-challenge/_Pamo3hOMx37O-KHIioWO1P0hbTLZusEZ2a2iCf7PDQ
这个 url 返回的结果为 _Pamo3hOMx37O-KHIioWO1P0hbTLZusEZ2a2iCf7PDQ.8CyQk-SfDMR10nk7DuGBFA4IEpVhm1OySU-nSItD1BE (这个数据是随机的,每次都不一样)
当你做到后,按下回车键继续

Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
  /etc/letsencrypt/live/you.domain.com/fullchain.pem
  Your key file has been saved at:
  /etc/letsencrypt/live/you.domain.com/privkey.pem
  Your cert will expire on 2018-01-27. To obtain a new or tweaked
  version of this certificate in the future, simply run certbot
  again. To non-interactively renew *all* of your certificates, run
  "certbot renew"
- If you like Certbot, please consider supporting our work by:

  Donating to ISRG / Let's Encrypt:  https://letsencrypt.org/donate
  Donating to EFF:                    https://eff.org/donate-le


查看和管理当前服务器上的所有证书
my-server$ certbot certificates
使用 https 证书
以 nginx 为例

server {
    listen 80;
    listen 443 ssl;
    server_name you.domain.com;
    ssl_certificate /etc/letsencrypt/live/you.domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/you.domain.com/privkey.pem;

    # ...
}
letsencrypt 有什么限制
标准限制
同一个顶级域名下的二级域名,一周做多申请 20 个
一个域名一周最多申请 5 次
1 小时最多允许失败 5 次
请求频率需要小于 20 次/s
一个 ip 3 小时内最多创建 10 个账户
一个账户最多同时存在 300 个 pending 的审核
测试模式限制
在 certbot 命令后加上 --staging 参数,限制更松一些,但不是正式证书

同一个顶级域名下的二级域名,一周做多申请 30000 个
一个域名一周最多申请 30000 次
1 小时最多允许失败 60 次
一个 ip 3小时内最多创建 50 个账户
签发泛域名证书
先升级到最新版本的 certbot,然后

my-server$ certbot certonly -d *.aabbcc.com --manual --server https://acme-v02.api.letsencrypt.org/directory --preferred-challenges dns

类别: 无分类 |  评论(0) |  浏览(46) |  收藏
2018年10月27日 17:12:44

linux shell中"2>&1"含义

linux shell中"2>&1"含义

在计划任务中经常可以看到。例如我们公司的计划任务举例:
*/2 * * * * root cd /opt/xxxx/test_S1/html/xxxx/admin; php index.php task testOne >/dev/null 2>&1
*/2 * * * * root cd /opt/xxxx/test_S1/html/xxxx/admin; php index.php task testTwo >/dev/null 2>&1对于& 1 更准确的说应该是文件描述符 1,而1标识标准输出,stdout。
对于2 ,表示标准错误,stderr。
2>&1 的意思就是将标准错误重定向到标准输出。这里标准输出已经重定向到了 /dev/null。那么标准错误也会输出到/dev/null

可以把/dev/null 可以看作"黑洞". 它等价于一个只写文件. 所有写入它的内容都会永远丢失. 而尝试从它那儿读取内容则什么也读不到.

偶尔也可以把 & 在命令的最后加上,表示让程序后台执行。

为何2>&1要写在后面?

index.php task testOne >/dev/null 2>&1我们可以理解为,左边是标准输出,好,现在标准输出直接输入到 /dev/null 中,而2>&1是将标准错误重定向到标准输出,所以当程序产生错误的时候,相当于错误流向左边,而左边依旧是输入到/dev/null中。

可以理解为,如果写在中间,那会把隔断标准输出指定输出的文件

你可以用

ls 2>1测试一下,不会报没有2文件的错误,但会输出一个空的文件1;
ls xxx 2>1测试,没有xxx这个文件的错误输出到了1中;
ls xxx 2>&1测试,不会生成1这个文件了,不过错误跑到标准输出了;
ls xxx >out.txt 2>&1, 实际上可换成 ls xxx 1>out.txt 2>&1;重定向符号>默认是1,错误和输出都传到out.txt了。

类别: 无分类 |  评论(0) |  浏览(39) |  收藏
2018年10月20日 09:47:12

使用nginx做反代时遇到413 Request Entity Too Large的解决方法

在使用nginx做反向代理的时候,被反代的系统在上传文件的时候遇到413 错误 :Request Entity Too Large

原因是nginx限制了上传文件的大小,在nginx中可以配置最大允许的文件大小:

打开nginx主配置文件nginx.conf,找到http{},添加
client_max_body_size 50m;

这里设置成了最大50m,你可以根据自己系统的需求自己来进行设置

类别: 无分类 |  评论(0) |  浏览(48) |  收藏
2018年10月20日 09:44:03

解决上传文件时 nginx 413 Request Entity Too Large 错误

一个使用Tomcat 发布的站点,使用Nginx做了代理,在上传文件时发生以下错误:

<html>
<head><title>413 Request Entity Too Large</title></head>
<body bgcolor="white">
<center><h1>413 Request Entity Too Large</h1></center>
<hr><center>nginx/1.10.3</center>
</body>
</html>
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->
<!-- a padding to disable MSIE and Chrome friendly error page -->在使用文件上传组件如:webuploader 上传时只会提示文件上传失败,
具体的错误需要打开浏览器的开发者工具才能看到。


产生原因:

上传文件的大小超出了 Nginx 允许的最大值,如果没有配置的话,默认是1M;

解决方法:

修改Nginx的配置文件(一般是:nginx/nginx.conf),在 http{} 段中增大nginx上传文件大小限制


#设置允许发布内容为8M
client_max_body_size 8M;
client_body_buffer_size 128k;修改完成后,测试并重新加载配置文件

./nginx.exe -t
./nginx.exe -s reload


Spring MVC环境中文件上传大小和文件类型限制以及超大文件上传bug问题



如果使用的是struts2框架,就可以在struts.xml中添加配置项:


<constant name="struts.multipart.maxSize" value="52428800" />


如果运行的是php,那么还要检查php.ini,这个大小client_max_body_size要和php.ini中的如下值的最大值一致或者稍大,这样就不会因为提交数据大小不一致出现的错误。

post_max_size = 8M
upload_max_filesize = 6M修改完配置后,别忘记重新加载。

类别: 无分类 |  评论(0) |  浏览(39) |  收藏
2018年10月14日 11:33:00

浅析 4K 10bit和8bit有什么区别?

      我们目前主要接触到的图像多数是24bit或32bit颜色深度,它等于每通道8bit的 R、G、B或每通道8bitR、G、B、A色彩通道的相加,而8bit表示每个原色具有256个灰阶,即0-255对应色彩从黑到白的灰度级别,10bit表示单色彩通道具有1024个灰度级别,色阶范围是0-1023。8bit提供256个采样点,而10bit提供1024个采样点,其色彩精度是8bit的4倍。
      目前市销的显示器绝大多数都是6bit的TN和IPS面板,少数IPS面板可以达到8bit。国内著名的wiki、chd、hdchina等压制小组之前的作品都是8bit重编码,而官方的x264目前也只支持8bit,我们所用到的支持10bit编码的x264都是经过高手重编译的版本。普通MKV在6bit面板播放时,都是显示器以抖动(dither)的方式转换成8bit实现的。
      在此引用好像是taor大的一句话:“在二进制下对8bit的源数据进行有损编码,造成相同量化损失时,以8bit为100%的error(残差)时,9bit为50%的error,10bit为25%,12bit为6.25%……”其实就是说8bit转换为10bit是无损的,而10bit转换为8bit是有损的,换句话说,由低位深向高位深的转换是无损的,反之则是有损的。一般显示器由10位dither为8bit总要比8位dither为8bit的损失要小得多。因此,10bit片源在播放画质方面是极占优势的。
        而我们有时为了达到更佳画质,会极端的使用madvr解码器,普遍理解madvr解码器解码效果最好,那是因为madvr内部是不管你8bit还是10bit输入,都先升到16bit进行处理,最后再dither成8bitRGB32进行输出,所以它才能实现最高精度的转换和最小的error。而8bit转换成RGB后远远到达不了8bitRGB的16777216色(目前大部分高清视频就是属于8bit-Rec709的情况),只有用10bit及以上转换为8bitRGB才能达到16.7M色。
      由以上论述得出结论:10bit重编码,实际上就是用更高位深来进行数据的有损编码,而且bit越高效果越好。一句话:重编码压缩率更高,以更低的码率提供更接近原盘画质的还原效果,这已经足够我们去尝试了。各位,心里有个数没有哦?
      其实,10bit有自己的劣势,请看下文:
        1、首先也是最要命的就是不支持绝大多数的硬盘播放机,但解决方法也相对简单,由生产厂商推出相应的固件即可,问题是啥时才能推出呢?
        2、10bit不支持硬解,所以你的cpu不能太差;假如你为了追求画质而选择了madvr,那么甚至你的显卡也不要太差。
综上,10bit是否值得你去追求,呵呵,看过画质之后,自己评判吧。
续上:
        经过多次压制,得出简单硬性评判10bit码率的方法:
        相同压制参数下,10bit比8bit压制时间多20%,但画质可提升25%;
        相同体积,相同画质,10bit码率要比8bit低大约20%左右;
        相同码率,10bit比8bit体积节省将近20%左右。

类别: 无分类 |  评论(0) |  浏览(64) |  收藏
2018年07月18日 14:58:11

Nginx的 HTTP 499 状态码处理

1、前言

  今天在处理一个客户问题,遇到Nginx access log中出现大量的499状态码。实际场景是:客户的域名通过cname解析到我们的Nginx反向代理集群上来,客户的Web服务是由一个负载均衡提供外网IP进行访问,负载均衡后面挂了多个内网web站点业务服务器。出现的访问日志如下所示:



2、处理方法

    499错误是什么?让我们看看NGINX的源码中的定义:
  ngx_string(ngx_http_error_495_page), /* 495, https certificate error */
  ngx_string(ngx_http_error_496_page), /* 496, https no certificate */
  ngx_string(ngx_http_error_497_page), /* 497, http to https */
  ngx_string(ngx_http_error_404_page), /* 498, canceled */
  ngx_null_string,                    /* 499, client has closed connection */

     可以看到,499对应的是 “client has closed connection”。这很有可能是因为服务器端处理的时间过长,客户端“不耐烦”了。

      测试nginx发现如果两次提交post过快就会出现499的情况,看来是nginx认为是不安全的连接,主动拒绝了客户端的连接.

      在google上搜索到一英文论坛上有关于此错误的解决方法:
      proxy_ignore_client_abort on;
      Don’t know if this is safe.
      就是说要配置参数 proxy_ignore_client_abort on;
      表示代理服务端不要主要主动关闭客户端连接。

      以此配置重启nginx,问题果然得到解决。只是安全方面稍有欠缺,但比总是出现找不到服务器好多了。

类别: 无分类 |  评论(0) |  浏览(73) |  收藏
2018年06月25日 15:42:15

syntax error, unexpected '[', expecting ')' php版本低不支持数组写法

因为服务器安装的是PHP5.2,所以选用Thinkphp3.1.3,在本地测试没问题,部署到服务器上就报已下错误
具体报错:Parse error: syntax error, unexpected '[', expecting ')' in /www/web/hrly/public_html/hrly/App/Common/common.php on line 14


【答】

你的PHP 版本是5.2的 你的数组写法却是5.4的 5.2中用array() 不能用[]

楼上正解 [](5.4版本以上) => array()

类别: 无分类 |  评论(0) |  浏览(83) |  收藏
2018年05月20日 23:25:16

innodb log file size 配置估算以及修改


root@localhost:(none) 06:22:17>pager grep seq
PAGER set to 'grep seq'


root@localhost:(none) 06:30:33>show engine innodb status\G select sleep(60); show engine innodb status\G
Log sequence number 29532909727
1 row in set (0.00 sec)
1 row in set (1 min 0.00 sec)
Log sequence number 29641344616
1 row in set (0.01 sec)


root@localhost:(none) 06:32:03>nopager
PAGER set to stdout


root@localhost:(none) 06:32:17>select (29641344616-29532909727)60/1024/1024
-> ;
+----------------------------------------+
| (29641344616-29532909727)60/1024/1024 |
+----------------------------------------+
| 6204.69411850 |
+----------------------------------------+
1 row in set (0.00 sec)


MySQL5.7 修改my.cnf重启即可
小于MySQL5.7
1.修改 my.cnf: innodb_log_file_size
2.stop MySQL server
3.确保MySQL干净的停止
4.删除旧的log files
5.启动MySQL server

类别: 无分类 |  评论(0) |  浏览(124) |  收藏
2018年05月20日 09:14:05

ip段/数字,如192.168.0.1/24是什么意思?

ip段/数字,如192.168.0.1/24是什么意思?
2014年09月19日 12:17:24 阅读数:185706 网上到处可见IP字段/数字, 例如: 192.168.0.1/24 是什么意思?
首先来了解一下二进制的转换知识:

二进制数转换成十进制数 二进制的1101转化成十进制
1101(2)=1*2^0+0*2^1+1*2^2+1*2^3=1+0+4+8=13
转化成十进制要从右到左用二进制的每个数去乘以2的相应次方.
不过次方要从0开始
相反 用十进制的13除以2 每除一下将余数就记在旁边
最后按余数从下向上排列就可得到1101
十进制转二进制:

用2辗转相除至结果为1
将余数和最后的1从下向上倒序写 就是结果
例如302
302/2 = 151 余0
151/2 = 75 余1
75/2 = 37 余1
37/2 = 18 余1
18/2 = 9 余0
9/2 = 4 余1
4/2 = 2 余0
2/2 = 1 余0
1/2 = 0 余1
故二进制为100101110
子网掩码的前缀表示法!  11111111 11111111 1111111 00000000  24个1组成    转成成十进制等于 255.255.255.0    11111111 11111111 1111111 11111100  30个1组成    转成成十进制等于 255.255.255.252 [/pre]我们租用服务器/vps的时候,买多个IP地址,机房总是发一个类似 10.0.0.1/29 或 10.0.0.1/28 或 10.0.0.1/30 或 10.0.0.1/27 的地址给我们,对于不熟悉网络的人来说,真的莫名其妙。到底有哪些可用的IP? 网关,子网掩码,广播地址是什么?
为了方便大家,本文章提供了 可用IP查询,ip段/27 /29 /30 网关,子网掩码,广播地址查询
可用IP查询,IP段对应表

IP总数子网掩码C段个数/30/29/28/27/26/24/23/22/21/20/19/18/17/16
4255.255.255.2521/64
8255.255.255.2481/32
16255.255.255.2401/16
32255.255.255.2241/8
64255.255.255.1921/4
256255.255.255.01
512255.255.254.02
1024255.255.252.04
2048255.255.248.08
4096255.255.240.016
8192255.255.224.032
16384255.255.192.064
32768255.255.128.0128
65536255.255.0.0256

ip段/24 /25 /26 /27 /28 /29 /30
网关,子网掩码,广播地址查询
范例: 要查询204.45.116.184/29,先找到下表的 /29 表格,网关 这一列找到要查询的IP最后一位即.184,然后就可以看到可用IP范围(注:第一个IP是Gateway网关地址)和广播地址了。然后从上面的表找到 子网掩码即可。
/25 — 2 Subnets — 126 Hosts/Subnet
网络地址可用IP范围广播地址
.0.1-.126.127
.128.129-.254.255
/24 –1 Subnets – 254 Hosts/Subnet
代表一整个C段
网络地址可用IP范围广播地址
.0.1-.254.255
/30 — 64 Subnets — 2 Hosts/Subnet
网络地址可用IP范围广播地址
.0.1-.2.3
.4.5-.6.7
.8.9-.10.11
.12.13-.14.15
.16.17-.18.19
.20.21-.22.23
.24.25-.26.27
.28.29-.30.31
.32.33-.34.35
.36.37-.38.39
.40.41-.42.43
.44.45-.46.47
.48.49-.50.51
.52.53-.54.55
.56.57-.58.59
.60.61-.62.63
.64.65-.66.67
.68.69-.70.71
.72.73-.74.75
.76.77-.78.79
.80.81-.82.83
.84.85-.86.87
.88.89-.90.91
.92.93-.94.95
.96.97-.98.99
.100.101-.102.103
.104.105-.106.107
.108.109-.110.111
.112.113-.114.115
.116.117-.118.119
.120.121-.122.123
.124.125-.126.127
.128.129-.130.131
.132.133-.134.135
.136.137-.138.139
.140.141-.142.143
.144.145-.146.147
.148.149-.150.151
.152.153-.154.155
.156.157-.158.159
.160.161-.162.163
.164.165-.166.167
.168.169-.170.171
.172.173-.174.175
.176.177-.178.179
.180.181-.182.183
.184.185-.186.187
.188.189-.190.191
.192.193-.194.195
.196.197-.198.199
.200.201-.202.203
.204.205-.206.207
.208.209-.210.211
.212.213-.214.215
.216.217-.218.219
.220.221-.222.223
.224.225-.226.227
.228.229-.230.231
.232.233-.234.235
.236.237-.238.239
.240.241-.242.243
.244.245-.246.247
.248.249-.250.251
.252.253-.254.255
/26 — 4 Subnets — 62 Hosts/Subnet
网络地址可用IP范围广播地址
.0.1-.62.63
.64.65-.126.127
.128.129-.190.191
.192.193-.254.255
/27 — 8 Subnets — 30 Hosts/Subnet
网络地址可用IP范围广播地址
.0.1-.30.31
.32.33-.62.63
.64.65-.94.95
.96.97-.126.127
.128.129-.158.159
.160.161-.190.191
.192.193-.222.223
.224.225-.254.255
/28 — 16 Subnets — 14 Hosts/Subnet
网络地址可用IP范围广播地址
.0.1-.14.15
.16.17-.30.31
.32.33-.46.47
.48.49-.62.63
.64.65-.78.79
.80.81-.94.95
.96.97-.110.111
.112.113-.126.127
.128.129-.142.143
.144.145-.158.159
.160.161-.174.175
.176.177-.190.191
.192.193-.206.207
.208.209-.222.223
.224.225-.238.239
.240.241-.254.255
/29 — 32 Subnets — 6 Hosts/Subnet
网络地址可用IP范围广播地址
.0.1-.6.7
.8.9-.14.15
.16.17-.22.23
.24.25-.30.31
.32.33-.38.39
.40.41-.46.47
.48.49-.54.55
.56.57-.62.63
.64.65-.70.71
.72.73-.78.79
.80.81-.86.87
.88.89-.94.95
.96.97-.102.103
.104.105-.110.111
.112.113-.118.119
.120.121-.126.127
.128.129-.134.135
.136.137-.142.143
.144.145-.150.151
.152.153-.158.159
.160.161-.166.167
.168.169-.174.175
.176.177-.182.183
.184.185-.190.191
.192.193-.198.199
.200.201-.206.207
.208.209-.214.215
.216.217-.222.223
.224.225-.230.231
.232.233-.238.239
.240.241-.246.247
.248.249-.254.255



类别: 无分类 |  评论(0) |  浏览(202) |  收藏
2018年05月15日 16:03:21

How to calculate a good InnoDB log file size

How to calculate a good InnoDB log file size
Baron Schwartz | November 21, 2008 |  Posted In: Insight for DBAs

https://www.percona.com/blog/2008/11/21/how-to-calculate-a-good-innodb-log-file-size/

PREVIOUS POST NEXT POST
Peter wrote a post a while ago about choosing a good InnoDB log file size. Not to pick on Peter, but the post actually kind of talks about a lot of things and then doesn’t tell you how to choose a good log file size! So I thought I’d clarify it a little.

The basic point is that your log file needs to be big enough to let InnoDB optimize its I/O, but not so big that recovery takes a long time. That much Peter covered really well. But how do you choose that size? I’ll show you a rule of thumb that works pretty well.


In most cases, when people give you a formula for choosing a configuration setting, you should look at it with skepticism. But in this case you can calculate a reasonable value, believe it or not. Run these queries at your server’s peak usage time:

mysql> pager grep sequence
PAGER set to 'grep sequence'
mysql> show engine innodb statusG select sleep(60); show engine innodb statusG
Log sequence number 84 3836410803
1 row in set (0.06 sec)
1 row in set (1 min 0.00 sec)
Log sequence number 84 3838334638
1 row in set (0.05 sec)

Notice the log sequence number. That’s the total number of bytes written to the transaction log. So, now you can see how many MB have been written to the log in one minute. (The technique I showed here works on all versions of MySQL. In 5.0 and newer, you can just watch Innodb_os_log_written from SHOW GLOBAL STATUS, too.)

mysql> select (3838334638 - 3836410803) / 1024 / 1024 as MB_per_min;
+------------+
| MB_per_min |
+------------+
| 1.83471203 |
+------------+

As a rough rule of thumb, you can make the log big enough that it can hold at most an hour or so of logs. That’s generally plenty of data for InnoDB to work with; an hour’s worth is more than enough so that it can reorder the writes to use sequential I/O during the flushing and checkpointing process. At this rate, this server could use about 110 MB of logs, total. Round it up to 128 for good measure. Since there are two log files by default, divide that in half, and now you can set

innodb_log_file_size=64M

Does that look surprisingly small? It might. I commonly see log file sizes in the gigabyte ranges. But that’s generally a mistake. The server I used for the measurements above is a big one doing a lot of work, not a toy. Log file sizes can’t be left at the default 5MB for any real workload, but they often don’t need to be as big as you might think, either.

If this rule-of-thumb calculation ends up showing you that your log file size ought to be many gigabytes, well, you have a more active write workload. Perhaps you’re inserting a lot of big rows or something. In this case you might want to make the log smaller so you don’t end up with GB of logs. But also realize this: the recovery time depends not only on the total log file size, but the number of entries in it. If you’re writing huge entries to the log, fewer log entries will fit into a given log file size, which will generally make recovery faster than you might expect with a big log.

However, most of the time when I run this calculation, I end up finding that the log file size needs to be a lot smaller than it’s configured to be. In part that’s because InnoDB’s log entries are very compact. The other reason is that the common advice to size the logs as a fraction of the buffer pool size is just wrong.

One final note: huge buffer pools or really unusual workloads may require bigger (or smaller!) log sizes. This is where formulas break down and judgment and experience are needed. But this “rule of thumb” is generally a good sane place to start.

类别: 无分类 |  评论(0) |  浏览(137) |  收藏
2018年05月15日 15:34:09

Galera / MySQL: log block numbers mismatch

https://dba.stackexchange.com/questions/132409/galera-replication-error-log-block-numbers-mismatch

[Q]

I am attempting to get Galera replication working between two nodes.

I am finding these errors in my innobackup.backup.log file:

xtrabackup: error: log block numbers mismatch:
xtrabackup: error: expected log block no. 671400745, but got no. 679592737 from the log file.
xtrabackup: error: it looks like InnoDB log has wrapped around before xtrabackup could process all records due to either log copying being too slow, or log files being too small.
xtrabackup: Error: xtrabackup_copy_logfile() failed.

I am not sure where to do from here, I have googled a bit and didn't find anything that seemed like it could help me in my particular instance.

Any advice will be most appreciated.

[A]

I finally have solved this problem.

I found an article on launchpad that said to use this command to see if the problem was IO:
innobackupex --user=<username> --password=<password> --stream=tar -- ibbackup=/usr/bin/xtrabackup /tmp >/dev/null

This failed to, so I started thinking IO was not at fault.

I played around with this command and found this one to complete:
innobackupex --user=<username> --password=<password> --stream=xbstream --parallel=15 /tmp >/dev/null

This also showed that my ulimit was not high enough, so I had to set this higher for the backup to complete.

To force innobackupex to use xbstream instead of tar for the --stream option I but this in my.cnf on both of my galera servers:
[sst] streamfmt=xbstream

类别: 无分类 |  评论(3) |  浏览(370) |  收藏
2018年05月15日 15:28:02

Android数据存储五种方式总结

https://www.cnblogs.com/ITtangtang/p/3920916.html


本文介绍Android平台进行数据存储的五大方式,分别如下: 

    1 使用SharedPreferences存储数据

    2 文件存储数据     

    3 SQLite数据库存储数据

    4 使用ContentProvider存储数据

    5 网络存储数据

下面详细讲解这五种方式的特点

第一种: 使用SharedPreferences存储数据

    适用范围:保存少量的数据,且这些数据的格式非常简单:字符串型、基本类型的值。比如应用程序的各种配置信息(如是否打开音效、是否使用震动效果、小游戏的玩家积分等),解锁口 令密码等

    核心原理:保存基于XML文件存储的key-value键值对数据,通常用来存储一些简单的配置信息。通过DDMS的File Explorer面板,展开文件浏览树,很明显SharedPreferences数据总是存储在/data/data/<package name>/shared_prefs目录下。SharedPreferences对象本身只能获取数据而不支持存储和修改,存储修改是通过SharedPreferences.edit()获取的内部接口Editor对象实现。 SharedPreferences本身是一 个接口,程序无法直接创建SharedPreferences实例,只能通过Context提供的getSharedPreferences(String name, int mode)方法来获取SharedPreferences实例,该方法中name表示要操作的xml文件名,第二个参数具体如下:

                Context.MODE_PRIVATE: 指定该SharedPreferences数据只能被本应用程序读、写。

                Context.MODE_WORLD_READABLE:  指定该SharedPreferences数据能被其他应用程序读,但不能写。

                Context.MODE_WORLD_WRITEABLE:  指定该SharedPreferences数据能被其他应用程序读,写

Editor有如下主要重要方法:

                SharedPreferences.Editor clear():清空SharedPreferences里所有数据

                SharedPreferences.Editor putXxx(String key , xxx value): 向SharedPreferences存入指定key对应的数据,其中xxx 可以是boolean,float,int等各种基本类型据

                SharedPreferences.Editor remove(): 删除SharedPreferences中指定key对应的数据项

                boolean commit(): 当Editor编辑完成后,使用该方法提交修改

    实际案例:运行界面如下

                     

这里只提供了两个按钮和一个输入文本框,布局简单,故在此不给出界面布局文件了,程序核心代码如下:       

class ViewOcl implements View.OnClickListener{

        @Override
        public void onClick(View v) {

            switch(v.getId()){
            case R.id.btnSet:
                //步骤1:获取输入值
                String code = txtCode.getText().toString().trim();
                //步骤2-1:创建一个SharedPreferences.Editor接口对象,lock表示要写入的XML文件名,MODE_WORLD_WRITEABLE写操作
                SharedPreferences.Editor editor = getSharedPreferences("lock", MODE_WORLD_WRITEABLE).edit();
                //步骤2-2:将获取过来的值放入文件
                editor.putString("code", code);
                //步骤3:提交
                editor.commit();
                Toast.makeText(getApplicationContext(), "口令设置成功", Toast.LENGTH_LONG).show();
                break;
            case R.id.btnGet:
                //步骤1:创建一个SharedPreferences接口对象
                SharedPreferences read = getSharedPreferences("lock", MODE_WORLD_READABLE);
                //步骤2:获取文件中的值
                String value = read.getString("code", "");
                Toast.makeText(getApplicationContext(), "口令为:"+value, Toast.LENGTH_LONG).show();
               
                break;
               
            }
        }
       
    }读写其他应用的SharedPreferences: 步骤如下

                1、在创建SharedPreferences时,指定MODE_WORLD_READABLE模式,表明该SharedPreferences数据可以被其他程序读取

                2、创建其他应用程序对应的Context:

                    Context pvCount = createPackageContext("com.tony.app", Context.CONTEXT_IGNORE_SECURITY);这里的com.tony.app就是其他程序的包名

                3、使用其他程序的Context获取对应的SharedPreferences

                    SharedPreferences read = pvCount.getSharedPreferences("lock", Context.MODE_WORLD_READABLE);

                4、如果是写入数据,使用Editor接口即可,所有其他操作均和前面一致。

SharedPreferences对象与SQLite数据库相比,免去了创建数据库,创建表,写SQL语句等诸多操作,相对而言更加方便,简洁。但是SharedPreferences也有其自身缺陷,比如其职能存储boolean,int,float,long和String五种简单的数据类型,比如其无法进行条件查询等。所以不论SharedPreferences的数据存储操作是如何简单,它也只能是存储方式的一种补充,而无法完全替代如SQLite数据库这样的其他数据存储方式。



第二种: 文件存储数据

核心原理: Context提供了两个方法来打开数据文件里的文件IO流 FileInputStream openFileInput(String name); FileOutputStream(String name , int mode),这两个方法第一个参数 用于指定文件名,第二个参数指定打开文件的模式。具体有以下值可选:

            MODE_PRIVATE:为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容,如果想把新写入的内容追加到原文件中。可  以使用Context.MODE_APPEND

            MODE_APPEND:模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。

            MODE_WORLD_READABLE:表示当前文件可以被其他应用读取;

            MODE_WORLD_WRITEABLE:表示当前文件可以被其他应用写入。

除此之外,Context还提供了如下几个重要的方法:

            getDir(String name , int mode):在应用程序的数据文件夹下获取或者创建name对应的子目录

            File getFilesDir():获取该应用程序的数据文件夹得绝对路径

            String[] fileList():返回该应用数据文件夹的全部文件             

实际案例:界面沿用上图

            核心代码如下:

public String read() {
        try {
            FileInputStream inStream = this.openFileInput("message.txt");
            byte[] buffer = new byte[1024];
            int hasRead = 0;
            StringBuilder sb = new StringBuilder();
            while ((hasRead = inStream.read(buffer)) != -1) {
                sb.append(new String(buffer, 0, hasRead));
            }

            inStream.close();
            return sb.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
   
    public void write(String msg){
        // 步骤1:获取输入值
        if(msg == null) return;
        try {
            // 步骤2:创建一个FileOutputStream对象,MODE_APPEND追加模式
            FileOutputStream fos = openFileOutput("message.txt",
                    MODE_APPEND);
            // 步骤3:将获取过来的值放入文件
            fos.write(msg.getBytes());
            // 步骤4:关闭数据流
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }openFileOutput()方法的第一参数用于指定文件名称,不能包含路径分隔符“/” ,如果文件不存在,Android 会自动创建它。创建的文件保存在/data/data/<package name>/files目录,如: /data/data/cn.tony.app/files/message.txt,

下面讲解某些特殊文件读写需要注意的地方:

读写sdcard上的文件

其中读写步骤按如下进行:

1、调用Environment的getExternalStorageState()方法判断手机上是否插了sd卡,且应用程序具有读写SD卡的权限,如下代码将返回true

Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)

2、调用Environment.getExternalStorageDirectory()方法来获取外部存储器,也就是SD卡的目录,或者使用"/mnt/sdcard/"目录

3、使用IO流操作SD卡上的文件

注意点:手机应该已插入SD卡,对于模拟器而言,可通过mksdcard命令来创建虚拟存储卡

          必须在AndroidManifest.xml上配置读写SD卡的权限

<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

案例代码:

// 文件写操作函数
    private void write(String content) {
        if (Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED)) { // 如果sdcard存在
            File file = new File(Environment.getExternalStorageDirectory()
                    .toString()
                    + File.separator
                    + DIR
                    + File.separator
                    + FILENAME); // 定义File类对象
            if (!file.getParentFile().exists()) { // 父文件夹不存在
                file.getParentFile().mkdirs(); // 创建文件夹
            }
            PrintStream out = null; // 打印流对象用于输出
            try {
                out = new PrintStream(new FileOutputStream(file, true)); // 追加文件
                out.println(content);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (out != null) {
                    out.close(); // 关闭打印流
                }
            }
        } else { // SDCard不存在,使用Toast提示用户
            Toast.makeText(this, "保存失败,SD卡不存在!", Toast.LENGTH_LONG).show();
        }
    }

    // 文件读操作函数
    private String read() {

        if (Environment.getExternalStorageState().equals(
                Environment.MEDIA_MOUNTED)) { // 如果sdcard存在
            File file = new File(Environment.getExternalStorageDirectory()
                    .toString()
                    + File.separator
                    + DIR
                    + File.separator
                    + FILENAME); // 定义File类对象
            if (!file.getParentFile().exists()) { // 父文件夹不存在
                file.getParentFile().mkdirs(); // 创建文件夹
            }
            Scanner scan = null; // 扫描输入
            StringBuilder sb = new StringBuilder();
            try {
                scan = new Scanner(new FileInputStream(file)); // 实例化Scanner
                while (scan.hasNext()) { // 循环读取
                    sb.append(scan.next() + "\n"); // 设置文本
                }
                return sb.toString();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (scan != null) {
                    scan.close(); // 关闭打印流
                }
            }
        } else { // SDCard不存在,使用Toast提示用户
            Toast.makeText(this, "读取失败,SD卡不存在!", Toast.LENGTH_LONG).show();
        }
        return null;
    } 第三种:SQLite存储数据

SQLite是轻量级嵌入式数据库引擎,它支持 SQL 语言,并且只利用很少的内存就有很好的性能。现在的主流移动设备像Android、iPhone等都使用SQLite作为复杂数据的存储引擎,在我们为移动设备开发应用程序时,也许就要使用到SQLite来存储我们大量的数据,所以我们就需要掌握移动设备上的SQLite开发技巧

SQLiteDatabase类为我们提供了很多种方法,上面的代码中基本上囊括了大部分的数据库操作;对于添加、更新和删除来说,我们都可以使用

1 db.executeSQL(String sql); 
2 db.executeSQL(String sql, Object[] bindArgs);//sql语句中使用占位符,然后第二个参数是实际的参数集 除了统一的形式之外,他们还有各自的操作方法:

1 db.insert(String table, String nullColumnHack, ContentValues values); 
2 db.update(String table, Contentvalues values, String whereClause, String whereArgs); 
3 db.delete(String table, String whereClause, String whereArgs);以上三个方法的第一个参数都是表示要操作的表名;insert中的第二个参数表示如果插入的数据每一列都为空的话,需要指定此行中某一列的名称,系统将此列设置为NULL,不至于出现错误;insert中的第三个参数是ContentValues类型的变量,是键值对组成的Map,key代表列名,value代表该列要插入的值;update的第二个参数也很类似,只不过它是更新该字段key为最新的value值,第三个参数whereClause表示WHERE表达式,比如“age > ? and age < ?”等,最后的whereArgs参数是占位符的实际参数值;delete方法的参数也是一样

下面给出demo

数据的添加

1.使用insert方法

1 ContentValues cv = new ContentValues();//实例化一个ContentValues用来装载待插入的数据
2 cv.put("title","you are beautiful");//添加title
3 cv.put("weather","sun"); //添加weather
4 cv.put("context","xxxx"); //添加context
5 String publish = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
6                        .format(new Date());
7 cv.put("publish ",publish); //添加publish
8 db.insert("diary",null,cv);//执行插入操作2.使用execSQL方式来实现

String sql = "insert into user(username,password) values ('Jack Johnson','iLovePopMuisc');//插入操作的SQL语句
db.execSQL(sql);//执行SQL语句数据的删除

同样有2种方式可以实现

String whereClause = "username=?";//删除的条件
String[] whereArgs = {"Jack Johnson"};//删除的条件参数
db.delete("user",whereClause,whereArgs);//执行删除使用execSQL方式的实现

String sql = "delete from user where username='Jack Johnson'";//删除操作的SQL语句
db.execSQL(sql);//执行删除操作数据修改

同上,仍是2种方式

ContentValues cv = new ContentValues();//实例化ContentValues
cv.put("password","iHatePopMusic");//添加要更改的字段及内容
String whereClause = "username=?";//修改条件
String[] whereArgs = {"Jack Johnson"};//修改条件的参数
db.update("user",cv,whereClause,whereArgs);//执行修改使用execSQL方式的实现

String sql = "update user set password = 'iHatePopMusic' where username='Jack Johnson'";//修改的SQL语句
db.execSQL(sql);//执行修改数据查询

下面来说说查询操作。查询操作相对于上面的几种操作要复杂些,因为我们经常要面对着各种各样的查询条件,所以系统也考虑到这种复杂性,为我们提供了较为丰富的查询形式:

1 db.rawQuery(String sql, String[] selectionArgs); 
2 db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy); 
3 db.query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit); 
4 db.query(String distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit);  上面几种都是常用的查询方法,第一种最为简单,将所有的SQL语句都组织到一个字符串中,使用占位符代替实际参数,selectionArgs就是占位符实际参数集;

各参数说明:

table:表名称
colums:表示要查询的列所有名称集
selection:表示WHERE之后的条件语句,可以使用占位符
selectionArgs:条件语句的参数数组
groupBy:指定分组的列名
having:指定分组条件,配合groupBy使用
orderBy:y指定排序的列名
limit:指定分页参数
distinct:指定“true”或“false”表示要不要过滤重复值
Cursor:返回值,相当于结果集ResultSet
最后,他们同时返回一个Cursor对象,代表数据集的游标,有点类似于JavaSE中的ResultSet。下面是Cursor对象的常用方法:

1 c.move(int offset); //以当前位置为参考,移动到指定行 
2 c.moveToFirst();    //移动到第一行 
3 c.moveToLast();    //移动到最后一行 
4 c.moveToPosition(int position); //移动到指定行 
5 c.moveToPrevious(); //移动到前一行 
6 c.moveToNext();    //移动到下一行 
7 c.isFirst();        //是否指向第一条 
8 c.isLast();    //是否指向最后一条 
9 c.isBeforeFirst();  //是否指向第一条之前 
10 c.isAfterLast();    //是否指向最后一条之后 
11 c.isNull(int columnIndex);  //指定列是否为空(列基数为0) 
12 c.isClosed();      //游标是否已关闭 
13 c.getCount();      //总数据项数 
14 c.getPosition();    //返回当前游标所指向的行数 
15 c.getColumnIndex(String columnName);//返回某列名对应的列索引值 
16 c.getString(int columnIndex);  //返回当前行指定列的值 实现代码

String[] params =  {12345,123456};Cursor cursor = db.query("user",columns,"ID=?",params,null,null,null);//查询并获得游标
if(cursor.moveToFirst()){//判断游标是否为空
    for(int i=0;i<cursor.getCount();i++){
        cursor.move(i);//移动到指定记录
        String username = cursor.getString(cursor.getColumnIndex("username");
        String password = cursor.getString(cursor.getColumnIndex("password"));
    }
}通过rawQuery实现的带参数查询

Cursor result=db.rawQuery("SELECT ID, name, inventory FROM mytable");
//Cursor c = db.rawQuery("s name, inventory FROM mytable where ID=?",new Stirng[]{"123456"});   
result.moveToFirst();
while (!result.isAfterLast()) {
    int id=result.getInt(0);
    String name=result.getString(1);
    int inventory=result.getInt(2);
    // do something useful with these
    result.moveToNext();
}
result.close();

在上面的代码示例中,已经用到了这几个常用方法中的一些,关于更多的信息,大家可以参考官方文档中的说明。

最后当我们完成了对数据库的操作后,记得调用SQLiteDatabase的close()方法释放数据库连接,否则容易出现SQLiteException。

上面就是SQLite的基本应用,但在实际开发中,为了能够更好的管理和维护数据库,我们会封装一个继承自SQLiteOpenHelper类的数据库操作类,然后以这个类为基础,再封装我们的业务逻辑方法。

这里直接使用案例讲解:下面是案例demo的界面



SQLiteOpenHelper类介绍

SQLiteOpenHelper是SQLiteDatabase的一个帮助类,用来管理数据库的创建和版本的更新。一般是建立一个类继承它,并实现它的onCreate和onUpgrade方法。

方法名 方法描述
SQLiteOpenHelper(Context context,String name,SQLiteDatabase.CursorFactory factory,int version) 构造方法,其中

context 程序上下文环境 即:XXXActivity.this;

name :数据库名字;

factory:游标工厂,默认为null,即为使用默认工厂;

version 数据库版本号

onCreate(SQLiteDatabase db) 创建数据库时调用
onUpgrade(SQLiteDatabase db,int oldVersion , int newVersion) 版本更新时调用
getReadableDatabase() 创建或打开一个只读数据库
getWritableDatabase() 创建或打开一个读写数据库

首先创建数据库类

1 import android.content.Context;
2 import android.database.sqlite.SQLiteDatabase;
3 import android.database.sqlite.SQLiteDatabase.CursorFactory;
4 import android.database.sqlite.SQLiteOpenHelper;
5
6 public class SqliteDBHelper extends SQLiteOpenHelper {
7
8    // 步骤1:设置常数参量
9    private static final String DATABASE_NAME = "diary_db";
10    private static final int VERSION = 1;
11    private static final String TABLE_NAME = "diary";
12
13    // 步骤2:重载构造方法
14    public SqliteDBHelper(Context context) {
15        super(context, DATABASE_NAME, null, VERSION);
16    }
17
18    /*
19      * 参数介绍:context 程序上下文环境 即:XXXActivity.this
20      * name 数据库名字
21      * factory 接收数据,一般情况为null
22      * version 数据库版本号
23      */
24    public SqliteDBHelper(Context context, String name, CursorFactory factory,
25            int version) {
26        super(context, name, factory, version);
27    }
28    //数据库第一次被创建时,onCreate()会被调用
29    @Override
30    public void onCreate(SQLiteDatabase db) {
31        // 步骤3:数据库表的创建
32        String strSQL = "create table "
33                + TABLE_NAME
34                + "(tid integer primary key autoincrement,title varchar(20),weather varchar(10),context text,publish date)";
35        //步骤4:使用参数db,创建对象
36        db.execSQL(strSQL);
37    }
38    //数据库版本变化时,会调用onUpgrade()
39    @Override
40    public void onUpgrade(SQLiteDatabase arg0, int arg1, int arg2) {
41
42    }
43 }正如上面所述,数据库第一次创建时onCreate方法会被调用,我们可以执行创建表的语句,当系统发现版本变化之后,会调用onUpgrade方法,我们可以执行修改表结构等语句。

我们需要一个Dao,来封装我们所有的业务方法,代码如下:

1 import android.content.Context;
2 import android.database.Cursor;
3 import android.database.sqlite.SQLiteDatabase;
4
5 import com.chinasoft.dbhelper.SqliteDBHelper;
6
7 public class DiaryDao {
8
9    private SqliteDBHelper sqliteDBHelper;
10    private SQLiteDatabase db;
11
12    // 重写构造方法
13    public DiaryDao(Context context) {
14        this.sqliteDBHelper = new SqliteDBHelper(context);
15        db = sqliteDBHelper.getWritableDatabase();
16    }
17
18    // 读操作
19    public String execQuery(final String strSQL) {
20        try {
21            System.out.println("strSQL>" + strSQL);
22            // Cursor相当于JDBC中的ResultSet
23            Cursor cursor = db.rawQuery(strSQL, null);
24            // 始终让cursor指向数据库表的第1行记录
25            cursor.moveToFirst();
26            // 定义一个StringBuffer的对象,用于动态拼接字符串
27            StringBuffer sb = new StringBuffer();
28            // 循环游标,如果不是最后一项记录
29            while (!cursor.isAfterLast()) {
30                sb.append(cursor.getInt(0) + "/" + cursor.getString(1) + "/"
31                        + cursor.getString(2) + "/" + cursor.getString(3) + "/"
32                        + cursor.getString(4)+"#");
33                //cursor游标移动
34                cursor.moveToNext();
35            }
36            db.close();
37            return sb.deleteCharAt(sb.length()-1).toString();
38        } catch (RuntimeException e) {
39            e.printStackTrace();
40            return null;
41        }
42
43    }
44
45    // 写操作
46    public boolean execOther(final String strSQL) {
47        db.beginTransaction();  //开始事务
48        try {
49            System.out.println("strSQL" + strSQL);
50            db.execSQL(strSQL);
51            db.setTransactionSuccessful();  //设置事务成功完成
52            db.close();
53            return true;
54        } catch (RuntimeException e) {
55            e.printStackTrace();
56            return false;
57        }finally { 
58            db.endTransaction();    //结束事务 
59        } 
60
61    }
62 }我们在Dao构造方法中实例化sqliteDBHelper并获取一个SQLiteDatabase对象,作为整个应用的数据库实例;在增删改信息时,我们采用了事务处理,确保数据完整性;最后要注意释放数据库资源db.close(),这一个步骤在我们整个应用关闭时执行,这个环节容易被忘记,所以朋友们要注意。

我们获取数据库实例时使用了getWritableDatabase()方法,也许朋友们会有疑问,在getWritableDatabase()和getReadableDatabase()中,你为什么选择前者作为整个应用的数据库实例呢?在这里我想和大家着重分析一下这一点。

我们来看一下SQLiteOpenHelper中的getReadableDatabase()方法:

1 public synchronized SQLiteDatabase getReadableDatabase() { 
2    if (mDatabase != null && mDatabase.isOpen()) { 
3        // 如果发现mDatabase不为空并且已经打开则直接返回 
4        return mDatabase; 
5    } 

7    if (mIsInitializing) { 
8        // 如果正在初始化则抛出异常 
9        throw new IllegalStateException("getReadableDatabase called recursively"); 
10    } 
11 
12    // 开始实例化数据库mDatabase 
13 
14    try { 
15        // 注意这里是调用了getWritableDatabase()方法 
16        return getWritableDatabase(); 
17    } catch (SQLiteException e) { 
18        if (mName == null) 
19            throw e; // Can't open a temp database read-only! 
20        Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", e); 
21    } 
22 
23    // 如果无法以可读写模式打开数据库 则以只读方式打开 
24 
25    SQLiteDatabase db = null; 
26    try { 
27        mIsInitializing = true; 
28        String path = mContext.getDatabasePath(mName).getPath();// 获取数据库路径 
29        // 以只读方式打开数据库 
30        db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY); 
31        if (db.getVersion() != mNewVersion) { 
32            throw new SQLiteException("Can't upgrade read-only database from version " + db.getVersion() + " to " 
33                    + mNewVersion + ": " + path); 
34        } 
35 
36        onOpen(db); 
37        Log.w(TAG, "Opened " + mName + " in read-only mode"); 
38        mDatabase = db;// 为mDatabase指定新打开的数据库 
39        return mDatabase;// 返回打开的数据库 
40    } finally { 
41        mIsInitializing = false; 
42        if (db != null && db != mDatabase) 
43            db.close(); 
44    } 
45 }在getReadableDatabase()方法中,首先判断是否已存在数据库实例并且是打开状态,如果是,则直接返回该实例,否则试图获取一个可读写模式的数据库实例,如果遇到磁盘空间已满等情况获取失败的话,再以只读模式打开数据库,获取数据库实例并返回,然后为mDatabase赋值为最新打开的数据库实例。既然有可能调用到getWritableDatabase()方法,我们就要看一下了:

public synchronized SQLiteDatabase getWritableDatabase() { 
    if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) { 
        // 如果mDatabase不为空已打开并且不是只读模式 则返回该实例 
        return mDatabase; 
    } 
 
    if (mIsInitializing) { 
        throw new IllegalStateException("getWritableDatabase called recursively"); 
    } 
 
    // If we have a read-only database open, someone could be using it 
    // (though they shouldn't), which would cause a lock to be held on 
    // the file, and our attempts to open the database read-write would 
    // fail waiting for the file lock. To prevent that, we acquire the 
    // lock on the read-only database, which shuts out other users. 
 
    boolean success = false; 
    SQLiteDatabase db = null; 
    // 如果mDatabase不为空则加锁 阻止其他的操作 
    if (mDatabase != null) 
        mDatabase.lock(); 
    try { 
        mIsInitializing = true; 
        if (mName == null) { 
            db = SQLiteDatabase.create(null); 
        } else { 
            // 打开或创建数据库 
            db = mContext.openOrCreateDatabase(mName, 0, mFactory); 
        } 
        // 获取数据库版本(如果刚创建的数据库,版本为0) 
        int version = db.getVersion(); 
        // 比较版本(我们代码中的版本mNewVersion为1) 
        if (version != mNewVersion) { 
            db.beginTransaction();// 开始事务 
            try { 
                if (version == 0) { 
                    // 执行我们的onCreate方法 
                    onCreate(db); 
                } else { 
                    // 如果我们应用升级了mNewVersion为2,而原版本为1则执行onUpgrade方法 
                    onUpgrade(db, version, mNewVersion); 
                } 
                db.setVersion(mNewVersion);// 设置最新版本 
                db.setTransactionSuccessful();// 设置事务成功 
            } finally { 
                db.endTransaction();// 结束事务 
            } 
        } 
 
        onOpen(db); 
        success = true; 
        return db;// 返回可读写模式的数据库实例 
    } finally { 
        mIsInitializing = false; 
        if (success) { 
            // 打开成功 
            if (mDatabase != null) { 
                // 如果mDatabase有值则先关闭 
                try { 
                    mDatabase.close(); 
                } catch (Exception e) { 
                } 
                mDatabase.unlock();// 解锁 
            } 
            mDatabase = db;// 赋值给mDatabase 
        } else { 
            // 打开失败的情况:解锁、关闭 
            if (mDatabase != null) 
                mDatabase.unlock(); 
            if (db != null) 
                db.close(); 
        } 
    } 
}大家可以看到,几个关键步骤是,首先判断mDatabase如果不为空已打开并不是只读模式则直接返回,否则如果mDatabase不为空则加锁,然后开始打开或创建数据库,比较版本,根据版本号来调用相应的方法,为数据库设置新版本号,最后释放旧的不为空的mDatabase并解锁,把新打开的数据库实例赋予mDatabase,并返回最新实例。

看完上面的过程之后,大家或许就清楚了许多,如果不是在遇到磁盘空间已满等情况,getReadableDatabase()一般都会返回和getWritableDatabase()一样的数据库实例,所以我们在DBManager构造方法中使用getWritableDatabase()获取整个应用所使用的数据库实例是可行的。当然如果你真的担心这种情况会发生,那么你可以先用getWritableDatabase()获取数据实例,如果遇到异常,再试图用getReadableDatabase()获取实例,当然这个时候你获取的实例只能读不能写了

最后,让我们看一下如何使用这些数据操作方法来显示数据,界面核心逻辑代码:

public class SQLiteActivity extends Activity {

    public DiaryDao diaryDao;

    //因为getWritableDatabase内部调用了mContext.openOrCreateDatabase(mName, 0, mFactory); 
    //所以要确保context已初始化,我们可以把实例化Dao的步骤放在Activity的onCreate里
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        diaryDao = new DiaryDao(SQLiteActivity.this);
        initDatabase();
    }

    class ViewOcl implements View.OnClickListener {

        @Override
        public void onClick(View v) {

            String strSQL;
            boolean flag;
            String message;
            switch (v.getId()) {
            case R.id.btnAdd:
                String title = txtTitle.getText().toString().trim();
                String weather = txtWeather.getText().toString().trim();;
                String context = txtContext.getText().toString().trim();;
                String publish = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                        .format(new Date());
                // 动态组件SQL语句
                strSQL = "insert into diary values(null,'" + title + "','"
                        + weather + "','" + context + "','" + publish + "')";
                flag = diaryDao.execOther(strSQL);
                //返回信息
                message = flag?"添加成功":"添加失败";
                Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
                break;
            case R.id.btnDelete:
                strSQL = "delete from diary where tid = 1";
                flag = diaryDao.execOther(strSQL);
                //返回信息
                message = flag?"删除成功":"删除失败";
                Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
                break;
            case R.id.btnQuery:
                strSQL = "select * from diary order by publish desc";
                String data = diaryDao.execQuery(strSQL);
                Toast.makeText(getApplicationContext(), data, Toast.LENGTH_LONG).show();
                break;
            case R.id.btnUpdate:
                strSQL = "update diary set title = '测试标题1-1' where tid = 1";
                flag = diaryDao.execOther(strSQL);
                //返回信息
                message = flag?"更新成功":"更新失败";
                Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
                break;
            }
        }
    }

    private void initDatabase() {
        // 创建数据库对象
        SqliteDBHelper sqliteDBHelper = new SqliteDBHelper(SQLiteActivity.this);
        sqliteDBHelper.getWritableDatabase();
        System.out.println("数据库创建成功");
    }
}

Android sqlite3数据库管理工具

Android SDK的tools目录下提供了一个sqlite3.exe工具,这是一个简单的sqlite数据库管理工具。开发者可以方便的使用其对sqlite数据库进行命令行的操作。

程序运行生成的*.db文件一般位于"/data/data/项目名(包括所处包名)/databases/*.db",因此要对数据库文件进行操作需要先找到数据库文件:

1、进入shell 命令

adb shell2、找到数据库文件

#cd data/data
#ls                --列出所有项目
#cd project_name  --进入所需项目名
#cd databases   
#ls                --列出现寸的数据库文件3、进入数据库

#sqlite3 test_db  --进入所需数据库会出现类似如下字样:

SQLite version 3.6.22
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite>至此,可对数据库进行sql操作。

4、sqlite常用命令

>.databases        --产看当前数据库
>.tables          --查看当前数据库中的表
>.help            --sqlite3帮助
>.schema            --各个表的生成语句

类别: 无分类 |  评论(0) |  浏览(82) |  收藏
2018年05月15日 15:24:48

Android变量保存的总结

Android系统app在后台运行时,由于内存不够或者清理内存后,会导致activity销毁或app进程被杀掉,此时变量数据的保存要注意:

以下几点时系统自动帮我们做的,我们不用关心:

1.用intent启动activity,intent中的数据;

2.Fragment使用setArguments设置参数,activity重启后可以直接使用getArguments获得原来设置的参数(Fragment参数传递推荐);

下面数据需要我们手动去保存和恢复:

1.如果进程被杀掉,app程序中使用的静态变量;

2.activity或Fragment被销毁时,其使用的临时变量;




Android系统由于内存问题回收app时,activity和Fragment都会调用onSaveInstanceState保存数据,我们只需复写即可,但一定要记得调用父类的此方法

类别: 无分类 |  评论(0) |  浏览(102) |  收藏
2018年05月10日 10:25:24

通达OA:未找到该群,是否未登录即时通讯

原因:
Office_IM服务被关闭,或因故退出关闭了

解决:
重新启动IM服务即可

类别: 无分类 |  评论(0) |  浏览(81) |  收藏
2018年05月08日 09:48:28

redis Out Of Memory allocating 56 bytes!

当出现以下错误时:

VirtualAlloc/COWAlloc fail! Out Of Memory allocating 42 bytes!


配置文件:
修改:maxmemory 1024000000

类别: 无分类 |  评论(0) |  浏览(140) |  收藏
2018年05月04日 10:55:42

mysql galera cluster集群的监控

一、集群复制状态检查

1、SHOW GLOBAL STATUS LIKE 'wsrep_%';

+------------------------------+-------------------------------------------------------------+
| Variable_name                | Value                                                      |
+------------------------------+-------------------------------------------------------------+
| wsrep_local_state_uuid      | 9f6a992a-7dd9-11e5-9f85-f760745ffb39                        |
| wsrep_protocol_version      | 7                                                          |
| wsrep_last_committed        | 53                                                          |
| wsrep_replicated            | 6                                                          |
| wsrep_replicated_bytes      | 1368                                                        |
| wsrep_repl_keys              | 9                                                          |
| wsrep_repl_keys_bytes        | 210                                                        |
| wsrep_repl_data_bytes        | 774                                                        |
| wsrep_repl_other_bytes      | 0                                                          |
| wsrep_received              | 37                                                          |
| wsrep_received_bytes        | 23347                                                      |
| wsrep_local_commits          | 0                                                          |
| wsrep_local_cert_failures    | 0                                                          |
| wsrep_local_replays          | 0                                                          |
| wsrep_local_send_queue      | 0                                                          |
| wsrep_local_send_queue_max  |2                                                         
| wsrep_local_send_queue_min  | 0                                                         
| wsrep_local_send_queue_avg  | 0.125000            |
| wsrep_local_recv_queue      | 0                                                          |
| wsrep_local_recv_queue_max  | 2                                      |
| wsrep_local_recv_queue_min  | 0                                              |
| wsrep_local_recv_queue_avg  | 0.027027                              |
| wsrep_local_cached_downto    | 14                                                          |
| wsrep_flow_control_paused_ns | 0                                                          |
| wsrep_flow_control_paused    | 0.000000                                                    |
| wsrep_flow_control_sent      | 0                                                          |
| wsrep_flow_control_recv      | 0                                                          |
| wsrep_cert_deps_distance    | 1.000000                                                    |
| wsrep_apply_oooe            | 0.100000                                                    |
| wsrep_apply_oool            | 0.000000                                                    |
| wsrep_apply_window          | 1.250000                                                    |
| wsrep_commit_oooe            | 0.000000                                                    |
| wsrep_commit_oool            | 0.000000                                                    |
| wsrep_commit_window          | 1.250000                                                    |
| wsrep_local_state            | 4                                                          |
| wsrep_local_state_comment    | Synced                                                      |
| wsrep_cert_index_size        | 10                                                          |
| wsrep_cert_bucket_count      | 22                                                          |
| wsrep_gcache_pool_size      | 27144                                                      |
| wsrep_causal_reads          | 0                                                          |
| wsrep_cert_interval          | 0.325000                                                    |
| wsrep_incoming_addresses    |  |
| wsrep_evs_delayed            |                                                            |
| wsrep_evs_evict_list        |                                                            |
| wsrep_evs_repl_latency      | 0/0/0/0/0                                                  |
| wsrep_evs_state              | OPERATIONAL                                                |
| wsrep_gcomm_uuid            | 5e28860a-829e-11e5-9c06-665d7fe4003d                        |
| wsrep_cluster_conf_id        | 3                                                          |
| wsrep_cluster_size          | 3                                                          |
| wsrep_cluster_state_uuid    | 9f6a992a-7dd9-11e5-9f85-f760745ffb39                        |
| wsrep_cluster_status        | Primary                                                    |
| wsrep_connected              | ON                                                          |
| wsrep_local_bf_aborts        | 0                                                          |
| wsrep_local_index            | 1                                                          |
| wsrep_provider_name          | Galera                                                      |
| wsrep_provider_vendor        | Codership Oy <info@codership.com>                          |
| wsrep_provider_version      | 3.12(rXXXX)                                                |
| wsrep_ready                  | ON                                                          |
+------------------------------+-------------------------------------------------------------+

wsrep_notify_cmd.sh——监控状态的变化。使用方法参见http://galeracluster.com/documentation-webpages/notificationcmd.html

2、wsrep_cluster_state_uuid显示了cluster的state UUID,由此可看出节点是否还是集群的一员

SHOW GLOBAL STATUS LIKE 'wsrep_cluster_state_uuid'

集群内每个节点的value都应该是一样的,否则说明该节点不在集群中了

+--------------------------+--------------------------------------+
| Variable_name                    | Value                                                  |
+--------------------------+--------------------------------------+
| wsrep_cluster_state_uuid | 9f6a992a-7dd9-11e5-9f85-f760745ffb39 |
+--------------------------+--------------------------------------+

3、wsrep_cluster_conf_id显示了整个集群的变化次数。所有节点都应相同,否则说明某个节点与集群断开了

4、wsrep_cluster_size显示了集群中节点的个数

5、wsrep_cluster_status显示集群里节点的主状态。标准返回primary。如返回non-Primary或其他值说明是多个节点改变导致的节点丢失或者脑裂。如果所有节点都返回不是Primary,则要重设quorum。具体参见http://galeracluster.com/documentation-webpages/quorumreset.html如果返回都正常,说明复制机制在每个节点都能正常工作,下一步该检查每个节点的状态确保他们都能收到write-set

show global status like 'wsrep_cluster_status';
+----------------------+---------+
| Variable_name        | Value  |
+----------------------+---------+
| wsrep_cluster_status | Primary |
+----------------------+---------+

二、检查节点状态

节点状态显示了集群中的节点接受和更新write-set状态,以及可能阻止复制的一些问题

1、wsrep_ready显示了节点是否可以接受queries。ON表示正常,如果是OFF几乎所有的query都会报错,报错信息提示“ERROR 1047 (08501) Unknown Command”

SHOW GLOBAL STATUS LIKE 'wsrep_ready';

+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| wsrep_ready  | ON    |
+---------------+-------+

2、SHOW GLOBAL STATUS LIKE 'wsrep_connected’显示该节点是否与其他节点有网络连接。(实验得知,当把某节点的网卡down掉之后,该值仍为on。说明网络还在)丢失连接的问题可能在于配置wsrep_cluster_address或wsrep_cluster_name的错误

+-----------------+-------+
| Variable_name  | Value |
+-----------------+-------+
| wsrep_connected | ON    |
+-----------------+-------+

3、wsrep_local_state_comment 以人能读懂的方式显示节点的状态,正常的返回值是Joining, Waiting on SST, Joined, Synced or Donor,返回Initialized说明已不在正常工作状态

+---------------------------+--------+
| Variable_name            | Value  |
+---------------------------+--------+
| wsrep_local_state_comment | Synced |
+---------------------------+--------+

三、查看复制的健康状态

通过Flow Control的反馈机制来管理复制进程。当本地收到的write-set超过某一阀值时,该节点会启动flow control来暂停复制直到它赶上进度。监控本地收到的请求和flow control,有如下几个参数:

1、wsrep_local_recv_queue_avg——平均请求队列长度。当返回值大于0时,说明apply write-sets比收write-set慢,有等待。堆积太多可能导致启动flow control

+----------------------------+----------+
| Variable_name              | Value    |
+----------------------------+----------+
| wsrep_local_recv_queue_avg | 0.027027 |
+----------------------------+----------+

wsrep_local_recv_queue_max 和 wsrep_local_recv_queue_min可以看队列设置的最大最小值

2、wsrep_flow_control_paused 显示了自从上次查询之后,节点由于flow control而暂停的时间占整个查询间隔时间比。总体反映节点落后集群的状况。如果返回值为1,说明自上次查询之后,节点一直在暂停状态。如果发现某节点频繁落后集群,则应该调整wsrep_slave_threads或者把节点剔除

+---------------------------+----------+
| Variable_name            | Value    |
+---------------------------+----------+
| wsrep_flow_control_paused | 0.000000 |
+---------------------------+----------+

3、wsrep_cert_deps_distance显示了平行apply的最低和最高排序编号或者sql编号之间的平均距离值。这代表了节点潜在的并行程度,和线程相关

+--------------------------+----------+
| Variable_name            | Value    |
+--------------------------+----------+
| wsrep_cert_deps_distance | 1.000000 |
+--------------------------+----------+

四、检测网络慢的问题

通过检查发送队列来看传出的连接状况

1、wsrep_local_send_queue_avg显示自上次查询之后的平均发送队列长度。比如网络瓶颈和flow control都可能是原因

+----------------------------+----------+
| Variable_name              | Value    |
+----------------------------+----------+
| wsrep_local_send_queue_avg | 0.033333 |
+----------------------------+----------+

wsrep_local_send_queue_max 和 wsrep_local_send_queue_min可以看队列设置的最大值和最小值

五、日志监控

在my.cnf中做如下配置

# wsrep Log Options

wsrep_log_conflicts=ON  #会将冲突信息写入错误日志中,例如两个节点同时写同一行数据

wsrep_provider_options="cert.log_conflicts=ON"    #复制过程中的错误信息写在日志中

wsrep_debug=ON    #显示debug 信息在日志中,其中也包括鉴权信息,例如账号密码。因此在生产环境中不开启

六、附加的日志

当某节点在从节点上应用一个事件失败时,数据库服务器会创建一个特殊的binary log文件。文件名默认是GRA_*.log

类别: 无分类 |  评论(0) |  浏览(149) |  收藏
2018年05月04日 10:48:44

记一次MySQL中Waiting for table metadata lock的解决方法

没鸟用,仅供记录参考!

最近项目中的数据库查询经常挂起,应用程序启动后也报操作超时。测试人员就说数据库又挂了(貌似他们眼中的连接失败,查询无果都是挂了),通过 show processlist 一看,满屏都是 Waiting for table metadata lock 状态的连接。第一反应就是kill掉这些连接,奈何连接实在太多,实在kill不过来,于是重启服务,貌似重启果真能解决90%的问题,但如果不找到问题原因,问题也肯定会再次出现。

在网上查询得知MySQL在进行一些alter table等DDL操作时,如果该表上有未提交的事务则会出现 Waiting for table metadata lock ,而一旦出现metadata lock,该表上的后续操作都会被阻塞(详见 http://www.bubuko.com/infodetail-1151112.html)。所以这个问题需从两方面解决:

1. 查看未提交事务
从 information_schema.innodb_trx 表中查看当前未提交的事务

select trx_state, trx_started, trx_mysql_thread_id, trx_query from information_schema.innodb_trx\G1(\G作为结束符时,MySQL Client会把结果以列模式展示,对于列比较长的表,展示更直观)

字段意义:

trx_state: 事务状态,一般为RUNNING
trx_started: 事务执行的起始时间,若时间较长,则要分析该事务是否合理
trx_mysql_thread_id: MySQL的线程ID,用于kill
trx_query: 事务中的sql
一般只要kill掉这些线程,DDL操作就不会Waiting for table metadata lock。

2. 调整锁超时阈值
lock_wait_timeout 表示获取metadata lock的超时(单位为秒),允许的值范围为1到31536000(1年)。 默认值为31536000。详见 https://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_lock_wait_timeout 。默认值为一年!!!已哭瞎!将其调整为30分钟

set session lock_wait_timeout = 1800;
set global lock_wait_timeout = 1800;1好让出现该问题时快速故障(failfast)

类别: 无分类 |  评论(0) |  浏览(151) |  收藏
2018年05月04日 10:40:55

一次关于Waiting for table metadata lock的处理

一个简单的DDL操作(表数据很少,访问高)执行很久,导致Waiting for table metadata lock。

版本:mysql5.5.17

查看:Innodb_buffer_pool_pages_free = 0

解决:set global innodb_stats_on_metadata=0

查看丁奇老大博客:原文http://dinglin.iteye.com/blog/1575840


这个问题来自冷之同学测试时候碰到的一个“诡异现象”。


1、 测试现象     

测试的库有很多数据,但是重启之后,只对一个表的5w条记录作查询。查询条件客户端控制,确保查询范围。innodb_buffer_pool_size设置为35G。

现象1:查询性能会出现大幅度抖动;

现象2:介入追查后发现,Innodb_buffer_pool_pages_free = 0


        其中bp剩余量这个是最直观异常的,因为访问的5w行记录撑死也不可能把35G内存吃光的。在QA同学确认没有别人在使用这个库的情况下。


2、过程和原因

        其实几乎确定还是有别的查询在访问的。所以打开general_log。 发现除了QA同学压的语句外,这个Server上还有一些监控语句。

        其中一个语句如下

select constraint_schema,table_name,constraint_name,constraint_type from information_schema.table_constraints where table_schema not in ('information_schema', 'mysql', 'test'); 

        这个语句访问了表 information_schema.table_constraints.

跟踪发现这个语句触发了读盘操作。原因是需要访问引擎的info()接口,而InnoDB此时又“顺手”做了更新索引统计的操作dict_update_statistics。

更新索引统计的基本流程是随机读取部分demo行。所以这个操作实际上是访问了这个Server里面的所有表,因此不只是访问5w行。

而且由于别的表事先没有被访问,就会导致读盘操作,也包括BP的LRU更新。


3、哪些表会触发

        不只是上面提到的table_constraints,information_schema库下的一下几个表,访问时候都会触发这个“顺手”操作。

information_schema.TABLES

information_schema.STATISTICS

information_schema.PARTITIONS

information_schema.KEY_COLUMN_USAGE

information_schema.TABLE_CONSTRAINTS

information_schema.REFERENTIAL_CONSTRAINTS

        其实还有 show table status ,也会触发这个操作,只是只处理单表,所以影响没那么明显。


4、修改

头痛医头的方法是把这些监控去掉。但实际上像TABLES、TABLE_CONSTRAINTS这些表,都是静态数据,访问时不作索引统计也没关系的。

另外一个方法就是把innodb_stats_on_metadata设置成off,这样上述说到的这些表访问都不会触发索引统计。

        实际上这个动态统计的功能已经不推荐了,官方已经在6.0以后增加参数控制DML期间也不作动态统计了。因此这个参数配置成off更合理些(默认是on)

类别: 无分类 |  评论(0) |  浏览(134) |  收藏
« 1 2345» Pages: ( 1/20 total )