提交 7b6462c2 编写于 作者: martianzhang's avatar martianzhang

first commit

上级
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
# tab_size = 4 spaces
[*.go]
indent_style = tab
indent_size = 4
trim_trailing_whitespace = true
Ask questions at [Gitter](https://gitter.im/xiaomi-dba/soar).
[Open an issue](https://github.com/xiaomi/soar/issues/new) to discuss your plans before doing any work on SOAR.
---
name: Bug Report
about: You're experiencing an issue with SOAR that is different than the documented behavior.
---
Please answer these questions before submitting your issue. Thanks!
1. What did you do?
If possible, provide a recipe for reproducing the error.
2. What did you expect to see?
3. What did you see instead?
4. What version of are you using (`soar -version`)?
---
name: Feature Request
about: If you have something you think SOAR could improve or add support for.
---
Please search the existing issues for relevant feature requests, add upvotes to pre-existing requests.
#### Feature Description
A written overview of the feature.
#### Use Case(s)
Any relevant use-cases that you see.
---
name: Question
about: If you have a question, please check out our other community resources instead of opening an issue.
---
Issues on GitHub are intended to be related to bugs or feature requests, so we recommend using our other community resources instead of asking here.
- [SOAR Doc](http://github.com/XiaoMi/soar/blob/master/README.md)
- Any other questions can be asked in the community [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/xiaomi-dba/soar)
soar
soar.darwin-386
soar.darwin-amd64
soar.linux-386
soar.linux-amd64
soar.windows-386
soar.windows-amd64
common/version.go
doc/blueprint/
*.iml
*.swp
*.log
coverage.*
y.output
.DS_Store
.vscode/
.idea
_tools/
TestMarkdown2Html.html
language: go
sudo: false
go:
- 1.10
before_install:
- go get -u gopkg.in/alecthomas/gometalinter.v1
- gometalinter.v1 --install
script:
- gometalinter.v1 --config doc/example/metalinter.json ./...
after_success:
- bash <(curl -s https://codecov.io/bash)
# 更新日志
## 2018-10
- 2018-10-20 开源先锋日(OSCAR)对外正式开源发布代码
## 2018-09
- 修复多个启发式建议不准确BUG,优化部分建议文案使得建议更清晰
- 基于TiDB Parser完善多个DDL类型语句的建议
- 新增lint report-type类型,支持Vim Plugin优化建议输出
- 更新整理项目文档,开源准备
- 2018-09-21 Gdevops SOAR首次对外进行技术分享宣传
## 2018-08
- 利用docker临时容器进行daily测试
- 添加main_test全功能回归测试
- 修复在测试中发现的问题
- mymysql合并MySQL8.0相关PR,修改vendor依赖
- 改善HeuristicRule中的文案
- 持续集成Vitess Parser的改进
- NewQuery4Audit结构体中引入TiDB Parser
- 通过TiAST完成大量与 DDL 相关的TODO
- 修改heuristic rules检查的返回值,提升拓展性
- 建议中引入Position,用于表示建议产生于SQL的位置
- 新增多个HeuristicRule
- Makefile中添加依赖检查,优化Makefile中逻辑,添加新功能
- 优化gometalinter性能,引入新的代码质量检测工具,提升代码质量
- 引入 retool 用于管理依赖的工具
- 优化 doc 文档
## 2018-07
- 补充文档,添加项目LOGO
- 改善代码质量提升测试覆盖度
- mymysql升级,支持MySQL 8.0
- 提供remove-comment小工具
- 提供索引重复检查小工具
- HeuristicRule新增RuleSpaceAfterDot
- 支持字符集和Collation不相同时的隐式数据类型转换的检查
## 2018-06
- 支持更多的SQL Rewrite规则
- 添加SQL执行超时限制
- 索引优化建议支持对约束的检查
- 修复数据采样中null值处理不正确的问题
- Explain支持last_query_cost
## 2018-05
- 添加数据采样功能
- 添加语句执行安全检查
- 支持DDL语法检查
- 支持DDL在测试环境的执行
- 支持隐式数据类型转换检查
- 支持索引去重
- 索引优化建议支持前缀索引
- 支持SQL Pretty输出
## 2018-04
- 支持语法检查
- 支持测试环境
- 支持MySQL原数据的获取
- 支持基于数据库环境信息给予索引优化建议
- 支持不依赖数据库原信息的简单索引优化建议
- 添加日志模块
- 引入配置文件
## 2018-03
- 基本架构设计
- 添加大量底层函数用于处理AST
- 添加Insert、Delete、Update转写成Select的基本函数
- 支持MySQL Explain信息输出
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
# This how we want to name the binary output
#
# use checkmake linter https://github.com/mrtazz/checkmake
# $ checkmake Makefile
#
BINARY=soar
PATH := ${GOPATH}/bin:$(PATH)
# These are the values we want to pass for VERSION and BUILD
BUILD_TIME=`date +%Y%m%d%H%M`
COMMIT_VERSION=`git rev-parse HEAD`
GO_VERSION_MIN=1.10
# Add mysql version for testing `MYSQL_VERSION=5.7 make docker`
# use mysql:latest as default
MYSQL_VERSION := $(or ${MYSQL_VERSION}, ${MYSQL_VERSION}, latest)
.PHONY: all
all: | fmt build
# Dependency check
.PHONY: deps
deps:
@echo "\033[92mDependency check\033[0m"
@bash ./deps.sh
# The retool tools.json is setup from retool-install.sh
retool sync
retool do gometalinter.v2 intall
# Code format
.PHONY: fmt
fmt:
@echo "\033[92mRun gofmt on all source files ...\033[0m"
@echo "gofmt -l -s -w ..."
@ret=0 && for d in $$(go list -f '{{.Dir}}' ./... | grep -v /vendor/); do \
gofmt -l -s -w $$d/*.go || ret=$$? ; \
done ; exit $$ret
# Run golang test cases
.PHONY: test
test:
@echo "\033[92mRun all test cases ...\033[0m"
go test ./...
@echo "test Success!"
# Code Coverage
# colorful coverage numerical >=90% GREEN, <80% RED, Other YELLOW
.PHONY: cover
cover: test
@echo "\033[92mRun test cover check ...\033[0m"
go test -coverpkg=./... -coverprofile=coverage.data ./... | column -t
go tool cover -html=coverage.data -o coverage.html
go tool cover -func=coverage.data -o coverage.txt
@tail -n 1 coverage.txt | awk '{sub(/%/, "", $$NF); \
if($$NF < 80) \
{print "\033[91m"$$0"%\033[0m"} \
else if ($$NF >= 90) \
{print "\033[92m"$$0"%\033[0m"} \
else \
{print "\033[93m"$$0"%\033[0m"}}'
# Builds the project
build: fmt tidb-parser
@echo "\033[92mBuilding ...\033[0m"
@bash ./genver.sh $(GO_VERSION_MIN)
@ret=0 && for d in $$(go list -f '{{if (eq .Name "main")}}{{.ImportPath}}{{end}}' ./...); do \
go build $$d || ret=$$? ; \
done ; exit $$ret
@echo "build Success!"
.PHONY: fast
fast:
@echo "\033[92mBuilding ...\033[0m"
@bash ./genver.sh $(GO_VERSION_MIN)
@ret=0 && for d in $$(go list -f '{{if (eq .Name "main")}}{{.ImportPath}}{{end}}' ./...); do \
go build $$d || ret=$$? ; \
done ; exit $$ret
@echo "build Success!"
# Installs our project: copies binaries
install: build
@echo "\033[92mInstall ...\033[0m"
go install ./...
@echo "install Success!"
# Generate doc use -list* command
.PHONY: doc
doc: fast
@echo "\033[92mAuto generate doc ...\033[0m"
./soar -list-heuristic-rules > doc/heuristic.md
./soar -list-rewrite-rules > doc/rewrite.md
./soar -list-report-types > doc/report_type.md
# Add or change a heuristic rule
.PHONY: heuristic
heuristic: doc docker
@echo "\033[92mUpdate Heuristic rule golden files ...\033[0m"
go test github.com/XiaoMi/soar/advisor -v -update -run TestListHeuristicRules
go test github.com/XiaoMi/soar/advisor -v -update -run TestMergeConflictHeuristicRules
docker stop soar-mysql 2>/dev/null || true
# Update vitess vendor
.PHONY: vitess
vitess:
@echo "\033[92mUpdate vitess deps ...\033[0m"
govendor fetch -v vitess.io/vitess/...
# Update tidb vendor
.PHONY: tidb
tidb:
@echo "\033[92mUpdate tidb deps ...\033[0m"
@echo -n "Current TiDB commit hash: "
@(cd ${GOPATH}/src/github.com/pingcap/tidb/ 2>/dev/null && git checkout master && git rev-parse HEAD) || echo "(init)"
go get -v -u github.com/pingcap/tidb/store/tikv
@echo -n "TiDB update to: "
@cd ${GOPATH}/src/github.com/pingcap/tidb/ && git rev-parse HEAD
# Update all vendor
.PHONY: vendor
vendor: vitess tidb
# make tidb parser
.PHONY: tidb-parser
tidb-parser: tidb
@echo "\033[92mimporting tidb sql parser ...\033[0m"
@cd ${GOPATH}/src/github.com/pingcap/tidb && git checkout -b soar ec9672cea6612481b1da845dbab620b7a5581ca4 && make parser
# gometalinter
# 如果有不想改的lint问题可以使用metalinter.sh加黑名单
#@bash doc/example/metalinter.sh
.PHONY: lint
lint: build
@echo "\033[92mRun linter check ...\033[0m"
CGO_ENABLED=0 retool do gometalinter.v2 -j 1 --config doc/example/metalinter.json ./...
retool do revive -formatter friendly --exclude vendor/... -config doc/example/revive.toml ./...
retool do golangci-lint --tests=false run
@echo "gometalinter check your code is pretty good"
.PHONY: release
release: deps build
@echo "\033[92mCross platform building for release ...\033[0m"
@for GOOS in darwin linux windows; do \
for GOARCH in 386 amd64; do \
for d in $$(go list -f '{{if (eq .Name "main")}}{{.ImportPath}}{{end}}' ./...); do \
b=$$(basename $${d}) ; \
echo "Building $${b}.$${GOOS}-$${GOARCH} ..."; \
GOOS=$${GOOS} GOARCH=$${GOARCH} go build -ldflags="-s -w" -v -o $${b}.$${GOOS}-$${GOARCH} $$d 2>/dev/null ; \
done ; \
done ;\
done
.PHONY: docker
docker:
@echo "\033[92mBuild mysql test enviorment\033[0m"
@docker stop soar-mysql 2>/dev/null || true
@echo "docker run --name soar-mysql mysql:$(MYSQL_VERSION)"
@docker run --name soar-mysql --rm -d \
-e MYSQL_ROOT_PASSWORD=1tIsB1g3rt \
-e MYSQL_DATABASE=sakila \
-p 3306:3306 \
-v `pwd`/doc/example/sakila.sql.gz:/docker-entrypoint-initdb.d/sakila.sql.gz \
mysql:$(MYSQL_VERSION)
@echo -n "waiting for sakila database initializing "
@while ! mysql -h 127.0.0.1 -u root sakila -p1tIsB1g3rt -NBe "do 1;" 2>/dev/null; do \
printf '.' ; \
sleep 1 ; \
done ; \
echo '.'
@echo "mysql test enviorment is ready!"
.PHONY: connect
connect:
mysql -h 127.0.0.1 -u root -p1tIsB1g3rt
.PHONY: main_test
main_test: install
@echo "\033[92mrunning main_test\033[0m"
@echo "soar -list-test-sqls | soar"
@./doc/example/main_test.sh
@echo "main_test Success!"
.PHONY: daily
daily: | deps fmt vendor tidb-parser docker cover doc lint release install main_test clean logo
@echo "\033[92mdaily build finished\033[0m"
.PHONY: logo
logo:
@echo "\033[93m"
@cat doc/images/logo.ascii
@echo "\033[m"
# Cleans our projects: deletes binaries
.PHONY: clean
clean:
@echo "\033[92mCleanup ...\033[0m"
go clean
@for GOOS in darwin linux windows; do \
for GOARCH in 386 amd64; do \
rm -f ${BINARY}.$${GOOS}-$${GOARCH} ;\
done ;\
done
rm -f ${BINARY} coverage.*
find . -name "*.log" -delete
git clean -fi
docker stop soar-mysql 2>/dev/null || true
Copyright 2018 Xiaomi, Inc. All Rights Reserved.
This product includes software developed by Xiaomi, Inc.
(http://www.mi.com/).
This product is licensed to you under the Apache License, Version 2.0
(the "License"). You may not use this product except in compliance with
the License.
![SOAR](http://github.com/XiaoMi/soar/raw/master/doc/images/logo.png)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/xiaomi-dba/soar) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](http://github.com/XiaoMi/soar/blob/master/LICENSE)
[文档](http://github.com/XiaoMi/soar/tree/master/doc) | [FAQ](http://github.com/XiaoMi/soar/blob/master/doc/FAQ.md) | [变更记录](http://github.com/XiaoMi/soar/blob/master/CHANGES.md) | [路线图](http://github.com/XiaoMi/soar/blob/master/doc/roadmap.md) | [English](http://github.com/XiaoMi/soar/blob/master/README_EN.md)
## SOAR
SOAR(SQL Optimizer And Rewriter)是一个对SQL进行优化和改写的自动化工具。 由小米人工智能与云平台的数据库团队开发与维护。
## 功能特点
* 跨平台支持(支持Linux, Mac环境,Windows环境理论上也支持,不过未全面测试)
* 支持基于启发式算法的语句优化
* 支持复杂查询的多列索引优化(UPDATE, INSERT, DELETE, SELECT)
* 支持EXPLAIN信息丰富解读
* 支持SQL指纹、压缩和美化
* 支持同一张表多条ALTER请求合并
* 支持自定义规则的SQL改写
## 快速入门
* [安装使用](http://github.com/XiaoMi/soar/blob/master/doc/install.md)
* [体系架构](http://github.com/XiaoMi/soar/blob/master/doc/structure.md)
* [配置文件](http://github.com/XiaoMi/soar/blob/master/doc/config.md)
* [常用命令](http://github.com/XiaoMi/soar/blob/master/doc/cheatsheet.md)
* [产品对比](http://github.com/XiaoMi/soar/blob/master/doc/comparison.md)
* [路线图](http://github.com/XiaoMi/soar/blob/master/doc/roadmap.md)
## 交流与反馈
* 欢迎通过Github Issues提交问题报告与建议
* QQ群: 779359816
* [Gitter](https://gitter.im/xiaomi-dba/soar)
## License
[Apache License 2.0](http://github.com/XiaoMi/soar/blob/master/LICENSE).
![SOAR](http://github.com/XiaoMi/soar/raw/master/doc/images/logo.png)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/xiaomi-dba/soar) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](http://github.com/XiaoMi/soar/blob/master/LICENSE)
[Docs](http://github.com/XiaoMi/soar/tree/master/doc) | [FAQ](http://github.com/XiaoMi/soar/blob/master/doc/FAQ_en.md) | [中文](http://github.com/XiaoMi/soar/blob/master/README.md)
## SOAR
SOAR (SQL Optimizer And Rewriter) is a tool, which can help SQL optimization and rewrite. It's developed and maintained by the DBA Team of Xiaomi AI&Cloud.
## Features
* Cross-platform support, such as Linux, Mac, and Windows
* Support Heuristic Rules Suggestion
* Support Complicate SQL Indexing Optimize
* Support EXPLAIN analyze for query plan
* Support SQL fingerprint, compress and built-in pretty print
* Support merge multi ALTER query into one SQL
* Support self-config rewrite rules from SQL Rewrite
* Suggestions were written in Chinese. But SOAR also gives many tools, which can be used without understanding Chinese.
## QuickStart
* [Install](http://github.com/XiaoMi/soar/blob/master/doc/install_en.md)
* [CheatSheet](http://github.com/XiaoMi/soar/blob/master/doc/cheatsheet_en.md)
* [Related works](http://github.com/XiaoMi/soar/blob/master/doc/comparison_en.md)
## Communication
* GitHub issues: bug reports, usage issues, feature requests
* [Gitter](https://gitter.im/xiaomi-dba/soar)
* IM QQ Group: 779359816
## License
[Apache License 2.0](http://github.com/XiaoMi/soar/blob/master/LICENSE).
/*
* Copyright 2018 Xiaomi, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package advisor contain heuristic rules, index rules and explain translator.
package advisor
/*
* Copyright 2018 Xiaomi, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package advisor
import (
"fmt"
"strings"
"github.com/XiaoMi/soar/common"
"github.com/XiaoMi/soar/database"
)
var explainRuleID int
// [EXP.XXX]Rule
var explainRules map[string]Rule
// [table_name]"suggest text"
var tablesSuggests map[string][]string
/*
var explainIgnoreTables = []string{
"dual",
"",
}
*/
// explain建议的形式
// Item: EXP.XXX
// Severity: L[0-8]
// Summary: full table scan, not use index, full index scan...
// Content: XX TABLE xxx
//
func checkExplainSelectType(exp *database.ExplainInfo) {
// 判断是否跳过不检查
if len(common.Config.ExplainWarnSelectType) == 1 {
if common.Config.ExplainWarnSelectType[0] == "" {
return
}
} else if len(common.Config.ExplainWarnSelectType) < 1 {
return
}
if exp.ExplainFormat == database.JSONFormatExplain {
// TODO
// JSON形式遍历分析不方便,转成Row格式也没有SelectType暂不处理
return
}
for _, v := range common.Config.ExplainWarnSelectType {
for _, row := range exp.ExplainRows {
if row.SelectType == v && v != "" {
tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("SelectType:%s", row.SelectType))
}
}
}
}
// 用户可以设置AccessType的建议级别,匹配到的查询会给出建议
func checkExplainAccessType(exp *database.ExplainInfo) {
// 判断是否跳过不检查
if len(common.Config.ExplainWarnAccessType) == 1 {
if common.Config.ExplainWarnAccessType[0] == "" {
return
}
} else if len(common.Config.ExplainWarnAccessType) < 1 {
return
}
rows := exp.ExplainRows
if exp.ExplainFormat == database.JSONFormatExplain {
// JSON形式遍历分析不方便,转成Row格式统一处理
rows = database.ConvertExplainJSON2Row(exp.ExplainJSON)
}
for _, v := range common.Config.ExplainWarnAccessType {
for _, row := range rows {
if row.AccessType == v && v != "" {
tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("Scalability:%s", row.Scalability))
}
}
}
}
// TODO:
/*
func checkExplainPossibleKeys(exp *database.ExplainInfo) {
// 判断是否跳过不检查
if common.Config.ExplainMinPossibleKeys == 0 {
return
}
rows := exp.ExplainRows
if exp.ExplainFormat == database.JSONFormatExplain {
// JSON形式遍历分析不方便,转成Row格式统一处理
rows = database.ConvertExplainJSON2Row(exp.ExplainJSON)
}
for _, row := range rows {
if len(row.PossibleKeys) < common.Config.ExplainMinPossibleKeys {
tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("PossibleKeys:%d < %d",
len(row.PossibleKeys), common.Config.ExplainMinPossibleKeys))
}
}
}
*/
// TODO:
/*
func checkExplainKeyLen(exp *database.ExplainInfo) {
}
*/
// TODO:
/*
func checkExplainKey(exp *database.ExplainInfo) {
// 小于最小使用试用key数量
//return intval($explainResult) < intval($userCond);
//explain-min-keys int
}
*/
func checkExplainRef(exp *database.ExplainInfo) {
rows := exp.ExplainRows
if exp.ExplainFormat == database.JSONFormatExplain {
// JSON形式遍历分析不方便,转成Row格式统一处理
rows = database.ConvertExplainJSON2Row(exp.ExplainJSON)
}
for i, row := range rows {
if strings.Join(row.Ref, "") == "NULL" || strings.Join(row.Ref, "") == "" {
if i == 0 && len(rows) > 1 {
continue
}
tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("Ref:null"))
}
}
}
func checkExplainRows(exp *database.ExplainInfo) {
// 判断是否跳过不检查
if common.Config.ExplainMaxRows <= 0 {
return
}
rows := exp.ExplainRows
if exp.ExplainFormat == database.JSONFormatExplain {
// JSON形式遍历分析不方便,转成Row格式统一处理
rows = database.ConvertExplainJSON2Row(exp.ExplainJSON)
}
for _, row := range rows {
if row.Rows >= common.Config.ExplainMaxRows {
tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("Rows:%d", row.Rows))
}
}
}
// TODO:
/*
func checkExplainExtra(exp *database.ExplainInfo) {
// 包含用户配置的逗号分隔关键词之一则提醒
// return self::contains($explainResult, $userCond);
// explain-warn-extra []string
}
*/
func checkExplainFiltered(exp *database.ExplainInfo) {
// 判断是否跳过不检查
if common.Config.ExplainMaxFiltered <= 0.001 {
return
}
rows := exp.ExplainRows
if exp.ExplainFormat == database.JSONFormatExplain {
// JSON形式遍历分析不方便,转成Row格式统一处理
rows = database.ConvertExplainJSON2Row(exp.ExplainJSON)
}
for i, row := range rows {
if i == 0 && len(rows) > 1 {
continue
}
if row.Filtered >= common.Config.ExplainMaxFiltered {
tablesSuggests[row.TableName] = append(tablesSuggests[row.TableName], fmt.Sprintf("Filtered:%.2f%s", row.Filtered, "%"))
}
}
}
// ExplainAdvisor 基于explain信息给出建议
func ExplainAdvisor(exp *database.ExplainInfo) map[string]Rule {
common.Log.Debug("ExplainAdvisor SQL: %v", exp.SQL)
explainRuleID = 0
explainRules = make(map[string]Rule)
tablesSuggests = make(map[string][]string)
checkExplainSelectType(exp)
checkExplainAccessType(exp)
checkExplainFiltered(exp)
checkExplainRef(exp)
checkExplainRows(exp)
// 打印explain table
content := database.PrintMarkdownExplainTable(exp)
if common.Config.ShowWarnings {
content += "\n" + database.MySQLExplainWarnings(exp)
}
// 对explain table中各项难于理解的值做解释
cases := database.ExplainInfoTranslator(exp)
// 添加last_query_cost
if common.Config.ShowLastQueryCost {
content += "\n" + database.MySQLExplainQueryCost(exp)
}
if content != "" {
explainRules["EXP.000"] = Rule{
Item: "EXP.000",
Severity: "L0",
Summary: "Explain信息",
Content: content,
Case: cases,
Func: (*Query4Audit).RuleOK,
}
}
/*
for t, s := range tablesSuggests {
// 检查explain对应的表是否需要跳过,如dual,空表等
ig := false
for _, ti := range explainIgnoreTables {
if ti == t {
ig = true
}
}
if ig {
continue
}
ruleId := fmt.Sprintf("EXP.%03d", explainRuleId+1)
explainRuleId = explainRuleId + 1
explainRules[ruleId] = Rule{
Item: ruleId,
Severity: "L0",
Summary: fmt.Sprintf("表 `%s` 查询效率不高", t),
Content: fmt.Sprint("原因:", strings.Join(s, ",")),
Case: "",
Func: (*Query4Audit).RuleOK,
}
}
*/
return explainRules
}
// DigestExplainText 分析用户输入的EXPLAIN信息
func DigestExplainText(text string) {
// explain信息就不要显示完美了,美不美自己看吧。
common.Config.IgnoreRules = append(common.Config.IgnoreRules, "OK")
if !IsIgnoreRule("EXP.") {
explainInfo, err := database.ParseExplainText(text)
if err != nil {
common.Log.Error("main ParseExplainText Error: %v", err)
return
}
expSuggest := ExplainAdvisor(explainInfo)
_, output := FormatSuggest("", common.Config.ReportType, expSuggest)
if common.Config.ReportType == "html" {
fmt.Println(common.MarkdownHTMLHeader())
fmt.Println(common.Markdown2HTML(output))
} else {
fmt.Println(output)
}
}
}
/*
* Copyright 2018 Xiaomi, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package advisor
import (
"testing"
"github.com/XiaoMi/soar/common"
)
func TestDigestExplainText(t *testing.T) {
var text = `+----+-------------+---------+-------+---------------------------------------------------------+-------------------+---------+---------------------------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------+-------+---------------------------------------------------------+-------------------+---------+---------------------------+------+-------------+
| 1 | SIMPLE | country | index | PRIMARY,country_id | country | 152 | NULL | 109 | Using index |
| 1 | SIMPLE | city | ref | idx_fk_country_id,idx_country_id_city,idx_all,idx_other | idx_fk_country_id | 2 | sakila.country.country_id | 2 | Using index |
+----+-------------+---------+-------+---------------------------------------------------------+-------------------+---------+---------------------------+------+-------------+`
common.Config.ReportType = "explain-digest"
err := common.GoldenDiff(func() { DigestExplainText(text) }, t.Name(), update)
if nil != err {
t.Fatal(err)
}
}
此差异已折叠。
此差异已折叠。
此差异已折叠。
/*
* Copyright 2018 Xiaomi, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package advisor
import (
"fmt"
"os"
"testing"
"github.com/XiaoMi/soar/common"
"github.com/XiaoMi/soar/env"
"github.com/kr/pretty"
"vitess.io/vitess/go/vt/sqlparser"
)
func init() {
common.BaseDir = common.DevPath
err := common.ParseConfig("")
if err != nil {
fmt.Println(err.Error())
}
vEnv, rEnv := env.BuildEnv()
if _, err = vEnv.Version(); err != nil {
fmt.Println(err.Error(), ", By pass all advisor test cases")
os.Exit(0)
}
if _, err := rEnv.Version(); err != nil {
fmt.Println(err.Error(), ", By pass all advisor test cases")
os.Exit(0)
}
}
// ARG.003
func TestRuleImplicitConversion(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName())
dsn := common.Config.OnlineDSN
common.Config.OnlineDSN = common.Config.TestDSN
vEnv, rEnv := env.BuildEnv()
defer vEnv.CleanUp()
initSQLs := []string{
`CREATE TABLE t1 (id int, title varchar(255) CHARSET utf8 COLLATE utf8_general_ci);`,
`CREATE TABLE t2 (id int, title varchar(255) CHARSET utf8mb4);`,
`CREATE TABLE t3 (id int, title varchar(255) CHARSET utf8 COLLATE utf8_bin);`,
}
for _, sql := range initSQLs {
vEnv.BuildVirtualEnv(rEnv, sql)
}
sqls := []string{
"SELECT * FROM t1 WHERE title >= 60;",
"SELECT * FROM t1, t2 WHERE t1.title = t2.title;",
"SELECT * FROM t1, t3 WHERE t1.title = t3.title;",
}
for _, sql := range sqls {
stmt, syntaxErr := sqlparser.Parse(sql)
if syntaxErr != nil {
common.Log.Critical("Syntax Error: %v, SQL: %s", syntaxErr, sql)
}
q := &Query4Audit{Query: sql, Stmt: stmt}
idxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)
if err != nil {
t.Error("NewAdvisor Error: ", err, "SQL: ", sql)
}
if idxAdvisor != nil {
rule := idxAdvisor.RuleImplicitConversion()
if rule.Item != "ARG.003" {
t.Error("Rule not match:", rule, "Expect : ARG.003, SQL:", sql)
}
}
}
common.Log.Debug("Exiting function: %s", common.GetFunctionName())
common.Config.OnlineDSN = dsn
}
// JOI.003 & JOI.004
func TestRuleImpossibleOuterJoin(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName())
sqls := []string{
`select city_id, city, country from city left outer join country using(country_id) WHERE city.city_id=59 and country.country='Algeria'`,
`select city_id, city, country from city left outer join country using(country_id) WHERE country.country='Algeria'`,
`select city_id, city, country from city left outer join country on city.country_id=country.country_id WHERE city.city_id IS NULL`,
}
vEnv, rEnv := env.BuildEnv()
defer vEnv.CleanUp()
for _, sql := range sqls {
stmt, syntaxErr := sqlparser.Parse(sql)
if syntaxErr != nil {
common.Log.Critical("Syntax Error: %v, SQL: %s", syntaxErr, sql)
}
q := &Query4Audit{Query: sql, Stmt: stmt}
if vEnv.BuildVirtualEnv(rEnv, q.Query) {
idxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)
if err != nil {
t.Error("NewAdvisor Error: ", err, "SQL: ", sql)
}
if idxAdvisor != nil {
rule := idxAdvisor.RuleImpossibleOuterJoin()
if rule.Item != "JOI.003" && rule.Item != "JOI.004" {
t.Error("Rule not match:", rule, "Expect : JOI.003 || JOI.004")
}
}
}
}
common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}
// GRP.001
func TestIndexAdvisorRuleGroupByConst(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName())
sqls := [][]string{
{
`select film_id, title from film where release_year='2006' group by release_year`,
`select film_id, title from film where release_year in ('2006') group by release_year`,
},
{
// 反面的例子
`select film_id, title from film where release_year in ('2006', '2007') group by release_year`,
},
}
vEnv, rEnv := env.BuildEnv()
defer vEnv.CleanUp()
for _, sql := range sqls[0] {
stmt, syntaxErr := sqlparser.Parse(sql)
if syntaxErr != nil {
common.Log.Critical("Syntax Error: %v, SQL: %s", syntaxErr, sql)
}
q := &Query4Audit{Query: sql, Stmt: stmt}
if vEnv.BuildVirtualEnv(rEnv, q.Query) {
idxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)
if err != nil {
t.Error("NewAdvisor Error: ", err, "SQL: ", sql)
}
if idxAdvisor != nil {
rule := idxAdvisor.RuleGroupByConst()
if rule.Item != "GRP.001" {
t.Error("Rule not match:", rule, "Expect : GRP.001")
}
}
}
}
for _, sql := range sqls[1] {
stmt, syntaxErr := sqlparser.Parse(sql)
if syntaxErr != nil {
common.Log.Critical("Syntax Error: %v, SQL: %s", syntaxErr, sql)
}
q := &Query4Audit{Query: sql, Stmt: stmt}
if vEnv.BuildVirtualEnv(rEnv, q.Query) {
idxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)
if err != nil {
t.Error("NewAdvisor Error: ", err, "SQL: ", sql)
}
if idxAdvisor != nil {
rule := idxAdvisor.RuleGroupByConst()
if rule.Item != "OK" {
t.Error("Rule not match:", rule, "Expect : OK")
}
}
}
}
common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}
// CLA.005
func TestIndexAdvisorRuleOrderByConst(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName())
sqls := [][]string{
{
`select film_id, title from film where release_year='2006' order by release_year;`,
`select film_id, title from film where release_year in ('2006') order by release_year;`,
},
{
// 反面的例子
`select film_id, title from film where release_year in ('2006', '2007') order by release_year;`,
},
}
vEnv, rEnv := env.BuildEnv()
defer vEnv.CleanUp()
for _, sql := range sqls[0] {
stmt, syntaxErr := sqlparser.Parse(sql)
if syntaxErr != nil {
common.Log.Critical("Syntax Error: %v, SQL: %s", syntaxErr, sql)
}
q := &Query4Audit{Query: sql, Stmt: stmt}
if vEnv.BuildVirtualEnv(rEnv, q.Query) {
idxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)
if err != nil {
t.Error("NewAdvisor Error: ", err, "SQL: ", sql)
}
if idxAdvisor != nil {
rule := idxAdvisor.RuleOrderByConst()
if rule.Item != "CLA.005" {
t.Error("Rule not match:", rule, "Expect : CLA.005")
}
}
}
}
for _, sql := range sqls[1] {
stmt, syntaxErr := sqlparser.Parse(sql)
if syntaxErr != nil {
common.Log.Critical("Syntax Error: %v, SQL: %s", syntaxErr, sql)
}
q := &Query4Audit{Query: sql, Stmt: stmt}
if vEnv.BuildVirtualEnv(rEnv, q.Query) {
idxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)
if err != nil {
t.Error("NewAdvisor Error: ", err, "SQL: ", sql)
}
if idxAdvisor != nil {
rule := idxAdvisor.RuleOrderByConst()
if rule.Item != "OK" {
t.Error("Rule not match:", rule, "Expect : OK")
}
}
}
}
common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}
// CLA.016
func TestRuleUpdatePrimaryKey(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName())
sqls := [][]string{
{
`update film set film_id = 1 where title='a';`,
},
{
// 反面的例子
`select film_id, title from film where release_year in ('2006', '2007') order by release_year;`,
},
}
vEnv, rEnv := env.BuildEnv()
defer vEnv.CleanUp()
for _, sql := range sqls[0] {
stmt, syntaxErr := sqlparser.Parse(sql)
if syntaxErr != nil {
common.Log.Critical("Syntax Error: %v, SQL: %s", syntaxErr, sql)
}
q := &Query4Audit{Query: sql, Stmt: stmt}
if vEnv.BuildVirtualEnv(rEnv, q.Query) {
idxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)
if err != nil {
t.Error("NewAdvisor Error: ", err, "SQL: ", sql)
}
if idxAdvisor != nil {
rule := idxAdvisor.RuleUpdatePrimaryKey()
if rule.Item != "CLA.016" {
t.Error("Rule not match:", rule.Item, "Expect : CLA.016")
}
}
}
}
for _, sql := range sqls[1] {
stmt, syntaxErr := sqlparser.Parse(sql)
if syntaxErr != nil {
common.Log.Critical("Syntax Error: %v, SQL: %s", syntaxErr, sql)
}
q := &Query4Audit{Query: sql, Stmt: stmt}
if vEnv.BuildVirtualEnv(rEnv, q.Query) {
idxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)
if err != nil {
t.Error("NewAdvisor Error: ", err, "SQL: ", sql)
}
if idxAdvisor != nil {
rule := idxAdvisor.RuleUpdatePrimaryKey()
if rule.Item != "OK" {
t.Error("Rule not match:", rule, "Expect : OK")
}
}
}
}
common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}
func TestIndexAdvise(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName())
vEnv, rEnv := env.BuildEnv()
defer vEnv.CleanUp()
for _, sql := range common.TestSQLs {
stmt, syntaxErr := sqlparser.Parse(sql)
if syntaxErr != nil {
common.Log.Critical("Syntax Error: %v, SQL: %s", syntaxErr, sql)
}
q := &Query4Audit{Query: sql, Stmt: stmt}
if vEnv.BuildVirtualEnv(rEnv, q.Query) {
idxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)
if err != nil {
t.Error("NewAdvisor Error: ", err, "SQL: ", sql)
}
if idxAdvisor != nil {
rule := idxAdvisor.IndexAdvise().Format()
if len(rule) > 0 {
pretty.Println(rule)
}
}
}
}
common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}
func TestIndexAdviseNoEnv(t *testing.T) {
common.Config.OnlineDSN.Disable = true
common.Log.Debug("Entering function: %s", common.GetFunctionName())
vEnv, rEnv := env.BuildEnv()
defer vEnv.CleanUp()
for _, sql := range common.TestSQLs {
stmt, syntaxErr := sqlparser.Parse(sql)
if syntaxErr != nil {
common.Log.Critical("Syntax Error: %v, SQL: %s", syntaxErr, sql)
}
q := &Query4Audit{Query: sql, Stmt: stmt}
if vEnv.BuildVirtualEnv(rEnv, q.Query) {
idxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)
if err != nil {
t.Error("NewAdvisor Error: ", err, "SQL: ", sql)
}
if idxAdvisor != nil {
rule := idxAdvisor.IndexAdvise().Format()
if len(rule) > 0 {
pretty.Println(rule)
}
}
}
}
common.Log.Debug("Exiting function: %s", common.GetFunctionName())
}
func TestDuplicateKeyChecker(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName())
_, rEnv := env.BuildEnv()
rule := DuplicateKeyChecker(rEnv, "sakila")
if len(rule) != 0 {
t.Errorf("got rules: %s", pretty.Sprint(rule))
}
}
func TestMergeAdvices(t *testing.T) {
dst := []IndexInfo{
{
Name: "test",
Database: "db",
Table: "tb",
ColumnDetails: []*common.Column{
{
Name: "test",
},
},
},
}
src := dst[0]
advise := mergeAdvices(dst, src)
if len(advise) != 1 {
t.Error(pretty.Sprint(advise))
}
}
func TestIdxColsTypeCheck(t *testing.T) {
common.Log.Debug("Entering function: %s", common.GetFunctionName())
sqls := []string{
`select city_id, city, country from city left outer join country using(country_id) WHERE city.city_id=59 and country.country='Algeria'`,
}
vEnv, rEnv := env.BuildEnv()
defer vEnv.CleanUp()
for _, sql := range sqls {
stmt, syntaxErr := sqlparser.Parse(sql)
if syntaxErr != nil {
common.Log.Critical("Syntax Error: %v, SQL: %s", syntaxErr, sql)
}
q := &Query4Audit{Query: sql, Stmt: stmt}
if vEnv.BuildVirtualEnv(rEnv, q.Query) {
idxAdvisor, err := NewAdvisor(vEnv, *rEnv, *q)
if err != nil {
t.Error("NewAdvisor Error: ", err, "SQL: ", sql)
}
idxList := []IndexInfo{
{
Name: "idx_fk_country_id",
Database: "sakila",
Table: "city",
ColumnDetails: []*common.Column{
{
Name: "country_id",
Character: "utf8",
DataType: "varchar(3000)",
},
},
},
}
if idxAdvisor != nil {
rule := idxAdvisor.idxColsTypeCheck(idxList)
if !(len(rule) > 0 && rule[0].DDL == "alter table `sakila`.`city` add index `idx_country_id` (`country_id`(N))") {
t.Error(pretty.Sprint(rule))
}
}
}
}
}
此差异已折叠。
/*
* Copyright 2018 Xiaomi, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package advisor
import (
"flag"
"testing"
"github.com/XiaoMi/soar/common"
)
var update = flag.Bool("update", false, "update .golden files")
func TestListTestSQLs(t *testing.T) {
err := common.GoldenDiff(func() { ListTestSQLs() }, t.Name(), update)
if nil != err {
t.Fatal(err)
}
}
func TestListHeuristicRules(t *testing.T) {
err := common.GoldenDiff(func() { ListHeuristicRules(HeuristicRules) }, t.Name(), update)
if nil != err {
t.Fatal(err)
}
}
func TestInBlackList(t *testing.T) {
common.BlackList = []string{"select"}
if !InBlackList("select 1") {
t.Error("should be true")
}
}
func TestIsIgnoreRule(t *testing.T) {
common.Config.IgnoreRules = []string{"test"}
if !IsIgnoreRule("test") {
t.Error("should be true")
}
}
## Explain信息
| id | select\_type | table | partitions | type | possible_keys | key | key\_len | ref | rows | filtered | scalability | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | *country* | NULL | index | PRIMARY,<br>country\_id | country | 152 | NULL | 0 | 0.00% | ☠️ **O(n)** | Using index |
| 1 | SIMPLE | *city* | NULL | ref | idx\_fk\_country\_id,<br>idx\_country\_id\_city,<br>idx\_all,<br>idx\_other | idx\_fk\_country\_id | 2 | sakila.country.country\_id | 0 | 0.00% | ☠️ **O(n)** | Using index |
### Explain信息解读
#### SelectType信息解读
* **SIMPLE**: 简单SELECT(不使用UNION或子查询等).
#### Type信息解读
* **index**: 全表扫描, 只是扫描表的时候按照索引次序进行而不是行. 主要优点就是避免了排序, 但是开销仍然非常大.
* **ref**: 连接不能基于关键字选择单个行, 可能查找到多个符合条件的行. 叫做ref是因为索引要跟某个参考值相比较. 这个参考值或者是一个数, 或者是来自一个表里的多表查询的结果值. 例:'SELECT * FROM tbl WHERE idx_col=expr;'.
#### Extra信息解读
* **Using index**: 只需通过索引就可以从表中获取列的信息, 无需额外去读取真实的行数据. 如果查询使用的列值仅仅是一个简单索引的部分值, 则会使用这种策略来优化查询.
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; 为列release_year添加索引,散粒度为: 0.10%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`), add index `idx_release_year` (`release_year`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; 为列release_year添加索引,散粒度为: 0.10%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`), add index `idx_release_year` (`release_year`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列release_year添加索引,散粒度为: 0.10%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_release_year` (`release_year`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列release_year添加索引,散粒度为: 0.10%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_release_year` (`release_year`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的address表添加索引", Content:"为列address添加索引,散粒度为: 100.00%; 为列district添加索引,散粒度为: 100.00%; ", Case:"ALTER TABLE `sakila`.`address` add index `idx_address` (`address`), add index `idx_district` (`district`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; 为列release_year添加索引,散粒度为: 0.10%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`), add index `idx_release_year` (`release_year`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; 为列release_year添加索引,散粒度为: 0.10%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`), add index `idx_release_year` (`release_year`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; 为列release_year添加索引,散粒度为: 0.10%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`), add index `idx_release_year` (`release_year`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; 为列release_year添加索引,散粒度为: 0.10%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`), add index `idx_release_year` (`release_year`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列release_year添加索引,散粒度为: 0.10%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_release_year` (`release_year`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列release_year添加索引,散粒度为: 0.10%; 为列length添加索引,散粒度为: 14.00%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_release_year` (`release_year`), add index `idx_length` (`length`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列release_year添加索引,散粒度为: 0.10%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_release_year` (`release_year`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; 为列release_year添加索引,散粒度为: 0.10%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`), add index `idx_release_year` (`release_year`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; 为列release_year添加索引,散粒度为: 0.10%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`), add index `idx_release_year` (`release_year`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; 为列release_year添加索引,散粒度为: 0.10%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`), add index `idx_release_year` (`release_year`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的country表添加索引", Content:"为列last_update添加索引,散粒度为: 0.92%; ", Case:"ALTER TABLE `sakila`.`country` add index `idx_last_update` (`last_update`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的city表添加索引", Content:"为列last_update添加索引,散粒度为: 0.17%; ", Case:"ALTER TABLE `sakila`.`city` add index `idx_last_update` (`last_update`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的country表添加索引", Content:"为列last_update添加索引,散粒度为: 0.92%; ", Case:"ALTER TABLE `sakila`.`country` add index `idx_last_update` (`last_update`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
"IDX.002": {Item:"", Severity:"L2", Summary:"为sakila库的city表添加索引", Content:"为列last_update添加索引,散粒度为: 0.17%; ", Case:"ALTER TABLE `sakila`.`city` add index `idx_last_update` (`last_update`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的country表添加索引", Content:"为列country添加索引,散粒度为: 100.00%; ", Case:"ALTER TABLE `sakila`.`country` add index `idx_country` (`country`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列length添加索引,散粒度为: 14.00%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_length` (`length`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的actor表添加索引", Content:"为列last_update添加索引,散粒度为: 0.50%; 为列first_name添加索引,散粒度为: 64.00%; ", Case:"ALTER TABLE `sakila`.`actor` add index `idx_last_update` (`last_update`), add index `idx_first_name` (`first_name`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的city表添加索引", Content:"为列city添加索引,散粒度为: 99.83%; ", Case:"ALTER TABLE `sakila`.`city` add index `idx_city` (`city`) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
map[string]advisor.Rule{
"IDX.001": {Item:"", Severity:"L2", Summary:"为sakila库的film表添加索引", Content:"为列description添加索引,散粒度为: 100.00%; ", Case:"ALTER TABLE `sakila`.`film` add index `idx_description` (`description`(255)) ;\n", Position:0, Func:func(*advisor.Query4Audit) advisor.Rule {...}},
}
此差异已折叠。
SELECT * FROM film WHERE length = 86;
SELECT * FROM film WHERE length IS NULL;
SELECT * FROM film HAVING title = 'abc';
SELECT * FROM sakila.film WHERE length >= 60;
SELECT * FROM sakila.film WHERE length >= '60';
SELECT * FROM film WHERE length BETWEEN 60 AND 84;
SELECT * FROM film WHERE title LIKE 'AIR%';
SELECT * FROM film WHERE title IS NOT NULL;
SELECT * FROM film WHERE length = 114 and title = 'ALABAMA DEVIL';
SELECT * FROM film WHERE length > 100 and title = 'ALABAMA DEVIL';
SELECT * FROM film WHERE length > 100 and language_id < 10 and title = 'xyz';
SELECT * FROM film WHERE length > 100 and language_id < 10;
SELECT release_year, sum(length) FROM film WHERE length = 123 AND language_id = 1 GROUP BY release_year;
SELECT release_year, sum(length) FROM film WHERE length >= 123 GROUP BY release_year;
SELECT release_year, language_id, sum(length) FROM film GROUP BY release_year, language_id;
SELECT release_year, sum(length) FROM film WHERE length = 123 GROUP BY release_year,(length+language_id);
SELECT release_year, sum(film_id) FROM film GROUP BY release_year;
SELECT * FROM address GROUP BY address,district;
SELECT title FROM film WHERE ABS(language_id) = 3 GROUP BY title;
SELECT language_id FROM film WHERE length = 123 GROUP BY release_year ORDER BY language_id;
SELECT release_year FROM film WHERE length = 123 GROUP BY release_year ORDER BY release_year;
SELECT * FROM film WHERE length = 123 ORDER BY release_year ASC, language_id DESC;
SELECT release_year FROM film WHERE length = 123 GROUP BY release_year ORDER BY release_year LIMIT 10;
SELECT * FROM film WHERE length = 123 ORDER BY release_year LIMIT 10;
SELECT * FROM film ORDER BY release_year LIMIT 10;
SELECT * FROM film WHERE length > 100 ORDER BY length LIMIT 10;
SELECT * FROM film WHERE length < 100 ORDER BY length LIMIT 10;
SELECT * FROM customer WHERE address_id in (224,510) ORDER BY last_name;
SELECT * FROM film WHERE release_year = 2016 AND length != 1 ORDER BY title;
SELECT title FROM film WHERE release_year = 1995;
SELECT title, replacement_cost FROM film WHERE language_id = 5 AND length = 70;
SELECT title FROM film WHERE language_id > 5 AND length > 70;
SELECT * FROM film WHERE length = 100 and title = 'xyz' ORDER BY release_year;
SELECT * FROM film WHERE length > 100 and title = 'xyz' ORDER BY release_year;
SELECT * FROM film WHERE length > 100 ORDER BY release_year;
SELECT * FROM city a INNER JOIN country b ON a.country_id=b.country_id;
SELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id;
SELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id;
SELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id WHERE b.last_update IS NULL;
SELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id WHERE a.last_update IS NULL;
SELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id UNION SELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id;
SELECT * FROM city a RIGHT JOIN country b ON a.country_id=b.country_id WHERE a.last_update IS NULL UNION SELECT * FROM city a LEFT JOIN country b ON a.country_id=b.country_id WHERE b.last_update IS NULL;
SELECT country_id, last_update FROM city NATURAL JOIN country;
SELECT country_id, last_update FROM city NATURAL LEFT JOIN country;
SELECT country_id, last_update FROM city NATURAL RIGHT JOIN country;
SELECT a.country_id, a.last_update FROM city a STRAIGHT_JOIN country b ON a.country_id=b.country_id;
SELECT d.deptno,d.dname,d.loc FROM scott.dept d WHERE d.deptno IN (SELECT e.deptno FROM scott.emp e);
SELECT visitor_id, url FROM (SELECT id FROM log WHERE ip="123.45.67.89" order by tsdesc limit 50, 10) I JOIN log ON (I.id=log.id) JOIN url ON (url.id=log.url_id) order by TS desc;
DELETE city, country FROM city INNER JOIN country using (country_id) WHERE city.city_id = 1;
DELETE city FROM city LEFT JOIN country ON city.country_id = country.country_id WHERE country.country IS NULL;
DELETE a1, a2 FROM city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id;
DELETE FROM a1, a2 USING city AS a1 INNER JOIN country AS a2 WHERE a1.country_id=a2.country_id;
DELETE FROM film WHERE length > 100;
UPDATE city INNER JOIN country USING(country_id) SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10;
UPDATE city INNER JOIN country ON city.country_id = country.country_id INNER JOIN address ON city.city_id = address.city_id SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.city_id=10;
UPDATE city, country SET city.city = 'Abha', city.last_update = '2006-02-15 04:45:25', country.country = 'Afghanistan' WHERE city.country_id = country.country_id AND city.city_id=10;
UPDATE film SET length = 10 WHERE language_id = 20;
INSERT INTO city (country_id) SELECT country_id FROM country;
INSERT INTO city (country_id) VALUES (1),(2),(3);
INSERT INTO city (country_id) VALUES (10);
INSERT INTO city (country_id) SELECT 10 FROM DUAL;
REPLACE INTO city (country_id) SELECT country_id FROM country;
REPLACE INTO city (country_id) VALUES (1),(2),(3);
REPLACE INTO city (country_id) VALUES (10);
REPLACE INTO city (country_id) SELECT 10 FROM DUAL;
SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM ( SELECT film_id FROM film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film ) film;
SELECT * FROM film WHERE language_id = (SELECT language_id FROM language LIMIT 1);
SELECT * FROM city i left JOIN country o ON i.city_id=o.country_id union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id;
SELECT * FROM (SELECT * FROM actor WHERE last_update='2006-02-15 04:34:33' and last_name='CHASE') t WHERE last_update='2006-02-15 04:34:33' and last_name='CHASE' GROUP BY first_name;
SELECT * FROM city i left JOIN country o ON i.city_id=o.country_id union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id;
SELECT * FROM city i left JOIN country o ON i.city_id=o.country_id WHERE o.country_id is null union SELECT * FROM city i right JOIN country o ON i.city_id=o.country_id WHERE i.city_id is null;
SELECT first_name,last_name,email FROM customer STRAIGHT_JOIN address ON customer.address_id=address.address_id;
SELECT ID,name FROM (SELECT address FROM customer_list WHERE SID=1 order by phone limit 50,10) a JOIN customer_list l ON (a.address=l.address) JOIN city c ON (c.city=l.city) order by phone desc;
SELECT * FROM film WHERE date(last_update)='2006-02-15';
SELECT last_update FROM film GROUP BY date(last_update);
SELECT last_update FROM film order by date(last_update);
SELECT description FROM film WHERE description IN('NEWS','asd') GROUP BY description;
alter table address add index idx_city_id(city_id);
alter table inventory add index `idx_store_film` (`store_id`,`film_id`);
alter table inventory add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`),add index `idx_store_film` (`store_id`,`film_id`);
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
#!/bin/bash
NEEDED_COMMANDS="mysql docker git go govendor retool"
for cmd in ${NEEDED_COMMANDS} ; do
if ! command -v "${cmd}" &> /dev/null ; then
echo -e "\033[91m${cmd} missing\033[0m"
exit 1
else
echo "${cmd} found"
fi
done
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册